Jini is one of Java's most exciting applications. With dynamic download of behavior, a universal code-execution platform, and simple construction of distributed systems, it promises to bring us to an era in which disparate software and hardware can simply locate each other and interact, without determining the communication protocol in advance.
Jini's conceptual simplicity is impressive, but newcomers may find the complexity of setting up and running a Jini system daunting. Just to run a simple Jini system, you need to launch six or seven processes:
- The Remote Method Invocation Daemon,
- A lookup service
- An HTTP server for the lookup service
- Your application-layer Jini service
- An HTTP server for your service
- The client for your Jini service
- If callback code for events needs to be downloaded: An HTTP server for the client
The following figure illustrates this deployment process.
These HTTP servers serve jar files or classfiles to the Jini client, which uses them to run the Jini proxy in its own process space; likewise, these servers can serve code on the client's behalf, for use in events.
HTTP servers, by themselves, are of course not Jini-specific; they are familiar from the World Wide Web. On the Web, the deployment scenario is simple: the clients make HTTP requests from the server. In a Jini system, though, any component may need code served on its behalf, and so we may have to set up many HTTP servers for even the simplest systems. As the Jini Testing Handbook at Jini.org puts it:
The codebase property and dynamic downloading of code can be a confusing hurdle for many Jini programmers.
We need a simple code-serving process. During development, we don't want to repeatedly stop and restart the system. Even after development, we'd like to simplify deployment of the system, with all its associated clients, services, and HTTP servers.
Several helpful solutions exist. I review these options for serving code, and discuss their pros and cons for various scenarios. In this article, I don't cover Jini basics, remote classloading principles, and other configuration problems, including log files, codebase definition, and security restrictions. (See Resources for information on these topics.) Instead, I focus on technologies useful in deploying a Jini system's code-serving portion. For this article, I assume you're using Jini's reference implementation.
Before reviewing some good solutions for Jini code-serving, let's eliminate some poor solutions. First, you may be tempted to give a
file URL as a codebase (such as
file:///usr/myjiniservice/lib/myjiniservice.jar), which may even work, but it limits your Jini system to one machine. Jini's whole point is to construct distributed systems, so don't use
Second, since you typically run one HTTP server per machine, using port 80, you may be tempted to serve all the code for your Jini system from one HTTP server using one port, using all classfiles from one directory or serving them in one jar file. Don't do it. This setup may work for development, but when you deploy the system on multiple machines, you will have to divide the classfiles among the machines, which means different HTTP servers. Copying all classfiles to all machines would obviate the purpose of dynamic classloading: an application dynamically downloads the classes it needs. The complexity of multiple HTTP servers, each serving only the code relevant to one Jini service or client, may be daunting, but you shouldn't bypass it, even during development.
Many HTTP-serving technologies are available, each with its own advantages, such as:
- Ease of development
- Ease of migration from development to deployment
- Low memory and CPU burden
- Close cohesion of Jini components with code served on their behalf, but loose coupling between different Jini components
- Compatibility with RMI activation
- Enterprise-class Web application features
Let's look at some of these technologies.
Script the system
To launch even one process in a Jini system, you'll probably use a script, since typing the long command lines required is impractical. Next, you run the system by calling all the scripts for all the processes from a master script.
To run a simple Jini system, your script should do the following:
- Delete old
rmidlog files and process-specific log files (for example, reggie, the lookup server in the reference implementation that generates its own log). These log files can help revive crashed processes for robustness, but during development, you may want to start with a clean slate each time.
rmidwith the appropriate policy file for security definition.
- Run an HTTP server for reggie. To start, you can use the simple HTTP server included in the Jini release. I'll discuss other HTTP servers, including Brazil, below.
- Run reggie.
- Run mahalo (for transactions) and other infrastructural services, each preceded by their respective HTTP server.
- Run an HTTP server for your Jini service.
- Run your Jini service.
- If the service's client needs notification of remote events or must allow callbacks from the server, run an HTTP server for the service's client.
- Run the client for your Jini service.
You need to arrange the temporal relation of these processes carefully. You should delete old files synchronously to eliminate the junk before starting other processes. You need to execute
rmid before any RMI clients/servers start. Likewise, you must run each HTTP server before its accompanying Jini service/client starts, to avoid a confusing warning message.
Once running, an HTTP server and its Jini component need to run in parallel with other HTTP/Jini pairs. On the other hand, the order of operation of various Jini services and clients isn't important -- Jini's beauty is the dynamic discovery process that lets those services and clients respond to new Jini components entering the system.
During development, you need another script to kill components that you are developing, until those components reach the stability needed to automatically update. (You can either leave HTTP servers running in the background or stop and restart them.)
For Unix systems, you can write shell scripts, but for Windows, the anemic DOS batch-file language makes writing decent scripts difficult and creates cross-platform compatibility problems. If you stick to portable scripting languages like Python, Ruby, and Perl, you can easily switch platforms, although you may have to install a language interpreter.
Scripting has several advantages. First, writing scripts in an interpreted language is usually easier than writing code for the same purpose in Java. Second, scripting, as opposed to launching various Jini components and HTTP servers in one JVM, lets you easily split up the system when moving from one development machine to multiple deployment machines. Thus, a script is useful when you want to start quickly and then have flexibility in reconfiguring your system.
A Python, Ruby, or Perl script that runs multiple system components has disadvantages, since it entails the overhead of installing and running the scripting interpreter. Also, running each component as a separate process can create an excessive memory and processing burden. Each JVM can consume many megabytes of RAM even with the simplest programs. This can be problematic in memory-limited devices on which Jini often runs.
In-process HTTP server
Alternatively, you can start an HTTP server in a separate thread inside the same process as the Jini component.
In-process HTTP servers reduce the number of processes, minimizing the resource burden. This solution also creates the correct cohesion between a Jini component and the code served on its behalf, minimizing the chance that incompatible code will run in the proxy/stub and in the originating component. Moreover, in a security-conscious system with authentication, this also has the security advantage of reducing the number of processes that must mutually "trust" each other.
However, any in-process HTTP server presents a serious problem: it makes using activatable services difficult. An activatable service is deactivated by
rmid, which then activates the service (sometimes in a new VM) when a client attempts to access it. If the HTTP server is in-process with the service, then the HTTP server is deactivated and activated together with the service. This may cause port-binding problems if two Jini components that include HTTP servers are then activated on the same machine. In addition, when a Jini client receives a Jini proxy from a lookup service, and then requests the proxy's classes from the HTTP server, the HTTP server may be inactive. In that case, of course, we expect the HTTP server to activate together with the Jini service, but this is impossible: until the service itself is accessed, the HTTP server remains inactive and unable to serve classes.
When launching an HTTP server in-process, be sure that its functionality meets your needs. Most in-process servers I describe below lack functionality found in enterprise-class HTTP servers, such as HTTPS and Web application modularity.
Choose the right in-process server
In choosing an in-process HTTP server, one obvious candidate is the minimal class server
com.sun.jini.tool.ClassServer, found in the Jini reference implementation. Just construct the
ClassServer with the appropriate web-root directory and an unused port. You should easily find the appropriate directory, since you are trying to serve code related to the component itself.
ClassServer is conveniently bundled with the Jini reference implementation, and can serve classfiles, jar files, and individual classfiles from within a jar file, you can run any other Java HTTP server in-process. Indeed, many Java HTTP servers have a convenient instantiation API. Even without another API, you can always call the Java HTTP server's
static main() method directly from your code.
You can choose from several lightweight HTTP servers designed for in-process use, each with its own special features. W. Keith Edwards, author of Core Jini, offers "The Service Writer's Toolkit" to simplify some common tasks for Jini developers. This toolkit's
ClassServer functionality and goes beyond to automate some troublesome tasks typically involved in code server setup.
ClassExporter saves you from manually setting up the codebase from the command line with a -D switch. Since the HTTP server runs in the same process as your Jini component, Edwards's class just sets the system property
java.rmi.server.codebase to the local host address (overriding any command-line settings), thus giving the Jini proxy the appropriate codebase.
Edwards's class also lets you avoid configuring an available port. You can choose the port if you want, but if you call the default constructor, then that constructor binds the socket to any available port. For most network servers, setting the port arbitrarily at runtime may seem pointless: how can any client know which port to access if the port number is not well-known? In this case, however, the port number is part of the codebase, which travels with the Jini proxy and RMI stubs as an annotation on the serialized object. Any Java code that deserializes the object and looks for the codebase can read not only the home machine's address, but also the appropriate port.
ClassExporter is an abstract class with two concrete implementations:
FSClassExporter ("FS" stands for "FileSystem") and
ContextClassExporter. The former resembles a standard HTTP server in that it serves files from a given path on the disk. It can also serve classes from inside a jar file.
ContextClassExporter, on the other hand, serves classes from its own classloader, after they have been loaded -- a neat trick.
ContextClassExporter has the aesthetic appeal of serving code right from the source application, and goes a step further by sharing with the client the same code used by the Jini service, without the need to duplicate classfiles. However,
ContextClassExporter has the potential security problem of offering all classes from the Jini component, not just the proxy and stub classes that should be downloaded.
FSClassExporter and ordinary HTTP servers have an advantage on that score.
ClassExporter is inefficient in that it serves classfiles individually, rather than in a jar file. It also shares the RMI activation problem with any solution that serves classes from inside the Jini component's process.
Other HTTP servers designed for embedding in a Jini component are available. The DynaServer JavaBean from the Java Jini Tools Project at Jini.org can serve code from the classpath and automatically set the codebase, similarly to Edwards's
ClassExporter. Sun's Brazil framework provides an API for dynamically serving content in HTTP (and other protocols). Brazil's design is lightweight enough to run in-process, yet easily extensible to provide exactly the enterprise-class functionality you need.
Unified interfaces for Jini systems
Several tools provide a single point for starting a Jini system. Such tools are useful for beginners, since they reduce the complexity of running multiple components individually.
The Jini 1.1 distribution includes a
StartService class, which can launch multiple Jini components in separate processes (using Java's
Runtime.exec() method). It can launch everything necessary, including
rmid; HTTP servers on various ports; and infrastructural Jini services, such as the reference lookup, transaction, and JavaSpace services. It can even launch your Jini clients and services.
StartService is not a standard Jini class, but rather part of Sun's reference implementation.
To use the
StartService graphical user interface (GUI), run the class
com.sun.jini.example.launcher.StartService. Start by examining the provided default property file (
jini11_win32.properties in the
examples directory next to
StartService.java). The default properties file defines required settings for all infrastructural components. You can use most of the defaults, but change the settings to give the appropriate local host address. (Don't use
127.0.0.1, which are useless when you work with more than one host.) Add new entries for your own Jini services and clients with their HTTP servers on different ports.
StartService is easy to set up, since configuring some properties is easier than writing a script. However, the
StartService GUI is less automated than a script, requiring some manual clicking every time you start the services. (The GUI has an option for stopping the processes, so at least you don't have to restart the GUI on each development iteration.) Another problem is the processing and memory burden of multiple VMs, since all components still launch in separate processes. Also, this system interface proves unusable when you move to multiple-machine deployment.
An alternative to
StartService is the open source Vagrant project, available on Jini.org. Vagrant runs an entire Jini system in one process, reducing the resource burden.
Enterprise-strength HTTP server
You may already be running an enterprise-strength server such as Apache on your network, perhaps to serve files for your Website or to provide Web services. In that case, you can simply configure your server to also serve code for each relevant Jini process. Since you want to serve code for different processes separately, configure your server to use a different port and web-root directory for each Jini process's code. Enterprise-strength servers let you define virtual servers (called Web applications in Java 2 Platform, Enterprise Edition (J2EE)) that run isolated from each other. Take advantage of this feature to isolate code-serving for different Jini components.
In an enterprise-strength HTTP server, you can take advantage of a server that is running anyway. You get all the power of an enterprise-strength server: Transport Layer Security (TLS), caching, HTTP/1.1 support, management tools, and so on. By simply reconfiguring the virtual servers, you can easily distribute the processes over several machines when the time comes. On the other hand, if your Jini components run on multiple hosts, you have to either run a server on each one or gather all serveable jar files onto the server machine, which increases coupling between Jini components.
Other creative approaches
You don't have to use HTTP to serve code. Any software service can do the trick, as long as it can send the bundle of bytes that constitute a jar file. Jerome Scheuring presented an interesting idea at JavaOne 2000 of a Jini service that serves code (see Resources). Until that is implemented fully -- a good challenge for the Jini Community -- HTTP is probably the simplest way to serve code.
Jiro, a network management system layered on top of Jini, takes an interesting approach. The Jiro developers recognize that "maintaining a class server is a laborious task" and provided a service in the Jiro Deployment Station that not only serves as a registry for publish/subscribe event notification, but also serves the code needed to execute all registered events.
Code-serving option summary
The table below summarizes the advantages and disadvantages of code-serving options, with hints for the appropriate usage of each one.
Deployment options for code servers
At your service
HTTP servers need to find their place in your Jini architecture as much as services, proxies, and clients. With the above techniques, you can structure your code-serving to account for your needs -- easy deployment, security, and other aspects of a well-designed Jini system.
Thanks to Jennifer McGinn and the Jini team at Sun for their helpful comments.