In Chapter 38, "Building Distributed Applications with the java.rmi Packages," you learned about the RMI API and how it is used to develop distributed applications. In this chapter, you'll develop a few distributed applications of your own to get some practical experience using RMI. You'll create an applet that uses RMI to connect to a server object, retrieve information, and display the information in a text area. You'll create a random number server and a client that accesses the server. You'll learn how to use class loaders and security managers to bootstrap the client from a remote host. You'll develop a program that contacts the remote registry of a host and obtains a list of the remote objects that it services. You'll also learn how to use RMI to develop remotely activatable objects that are persistent. When you finish this chapter, you'll have enough experience using RMI to build your own distributed applications.
In Chapter 38, you learned how to use RMI to develop simple client and server objects. The server object contained a main() method that set a security manager. It also registered the server object with the remote registry. The client performed a remote lookup of the server object in order to gain access to a local stub. It then used the stub to access the server object. The approach used in this example is common to most distributed applications that use RMI.
In order to use the client object, you copied its class file, the stub's class file, and the class file of the remote interface to the client computer. Although this may not have seemed to be an inconvenience at the time, one of the features of Java is the capability to automatically distribute software to clients. In this section, you'll learn how to use applets as clients for distributed applications. In the following section, you'll learn how to bootstrap clients from a remote host.
Listing 39.1 presents an applet that is used as a local client to access a remote server. The server generates news flashes that the client displays in a text area. The InfoClient.java file is stored in the ch39\info\client directory and is implemented as part of the ju.ch39.info.client package. Note that it imports the ju.ch39.info.server package. This package contains the classes and interfaces of the remote server.
package ju.ch39.info.client; import ju.ch39.info.server.*; import java.applet.*; import java.awt.*; import java.awt.event.*; import java.net.*; import java.rmi.*; public class InfoClient extends Applet { String text = "Click Update for an InfoServer update."; TextArea textArea = new TextArea(15,50); Button update = new Button("Update"); InfoServer server; public void init() { setLayout(new BorderLayout()); add("Center",textArea); update.addActionListener(new ButtonHandler()); add("South",update); try { URL hostURL = getCodeBase(); String host = hostURL.getHost(); server = (InfoServer) Naming.lookup("//"+host+"/InfoServer"); textArea.setText(text); } catch (Exception ex) { textArea.setText(ex.toString()); } } class ButtonHandler implements ActionListener { public void actionPerformed(ActionEvent ev){ String s=ev.getActionCommand(); if("Update".equals(s)){ try { String newText=server.getInfo(); text=newText+"\n"+text; textArea.setText(text); } catch (Exception ex){ textArea.setText(ex.toString()); } } } } }
You won't get to run the applet until a little later in this chapter. I have to show you how to compile, run, and install it first. However, I'll give you a preview of its operation so that you'll be able to more easily follow the discussion.
The InfoClient applet displays a text area and an Update button to the user, as shown in Figure 39.1. When you click the Update button, the applet invokes a method of an object that executes remotely on a Web server. The remote object returns a news update to the applet, and the applet displays this news update in the text area. See Figure 39.2.
FIGURE 39.1. The initial display of the InfoClient applet.
FIGURE 39.2. The applet's display (running under HotJava 1.1) is updated with news flashes from a remote server.
The processing performed by InfoClient consists of creating and laying out the applet's GUI, establishing access to the remote server object, and processing the event associated with the clicking of the Update button.
The server object implements the InfoServer interface. The server field variable is declared as the InfoServer type and is used to refer to the server object via a local stub. The init() method sets up the GUI and accesses the server object. The getCodebase() method retrieves the applet's URL, and the getHost() method retrieves the name of the host from which the applet was served. The lookup() method of the Naming class returns a local stub for the remote object named InfoServer. The InfoServer object executes on the Web server from which the applet was served. The local stub of the remote InfoServer object is referenced by the server variable.
The ButtonHandler class handles the clicking of the Update button by invoking the getInfo() method of the server object. This causes the server object to return a news flash in the form of a String. The returned String is then prepended to the text displayed in the text area.
Listing 39.2 contains the info.htm file that is used to display the applet. Note that it sets the CODEBASE attribute to the URL from which the applet's class file is retrieved. You must set this URL to the URL of your Web server. The actual applet is loaded from the following URL:
http://your.host.com/codebase/ju/ch39/info/client/InfoClient.class
You'll need to create a codebase directory on your Web server's root directory. You'll also store the compiled applet in the path /codebase/ju/ch39/info/client/. Refer to the section "Compiling and Installing the InfoServer Application" later in this chapter for information on how to compile both the client and server parts of the InfoServer application.
<HTML>
<HEAD> <TITLE>InfoClient</TITLE> </HEAD> <BODY> <APPLET CODEBASE="http://204.115.182.233/codebase/" CODE="ju.ch39.info.client.InfoClient.class" WIDTH=500 HEIGHT=300> [InfoClient Applet] </APPLET> </BODY> </HTML>
NOTE: Make sure that you substitute your server's host name or IP address for 204.115.182.233 in the InfoClient applet's CODEBASE attribute. You can use localhost if you are running both the client and server on the same computer.
Listing 39.3 contains the InfoServer interface that is implemented by the server object and referenced by the InfoClient applet. It is contained in the ju.ch39.info.server package and defines the getInfo() method.
package ju.ch39.info.server; import java.rmi.*; public interface InfoServer extends Remote { String getInfo() throws RemoteException; }
Listing 39.4 shows the InfoServerImpl class that implements the InfoServer interface. It declares and initializes the info array to a list of five news flashes. The getInfo() method retrieves the current date/time as a Date object and then randomly selects one of the elements of the info array. It converts the Date to a String, appends the info element, and returns the String to the invoking object.
The main() method sets the default security manager and creates an instance of the InfoServerImpl object. It invokes the rebind() method of the Naming class to register the new object with the remote registry under the name InfoServer. It then displays a message to the console window notifying you that the server object was successfully registered.
package ju.ch39.info.server; import java.rmi.*; import java.rmi.server.*; import java.util.*; public class InfoServerImpl extends UnicastRemoteObject implements InfoServer { String info[] = {"Gold is up to $500 per ounce.", "The Chargers beat the Raiders 41-0.", "The weather will be hot and sunny.", "Computer prices are coming down.", "Java-based PDAs are flooding the market."}; public InfoServerImpl() throws RemoteException { super(); } public String getInfo() throws RemoteException { String newInfo; Date date=new Date(); int n = new Double(100.0*Math.random()).intValue(); n %= info.length; newInfo=date.toString()+" "+info[n]; return newInfo; } public static void main(String args[]){ System.setSecurityManager(new RMISecurityManager()); try { InfoServerImpl instance = new InfoServerImpl(); Naming.rebind("///InfoServer", instance); System.out.println("InfoServer is registered."); } catch (Exception ex) { System.out.println(ex.toString()); } } }
Now that you've learned about each of the four source files, let's compile and install them on the server.
The InfoServer.java and InfoServerImpl.java files are in your ch39\info\server directory. Compile them and then use the rmic compiler to create a stub and skeleton for the InfoServerImpl class:
rmic ju.ch39.info.server.InfoServerImpl
Next, switch over to the client directory and compile InfoClient.java.
Your class files are ready to go. All you need to do is to move them to your Web server. Create the following codebase\ju\ch39\info\client and codebase\ju\ch39\ info\server paths on your Web server's root directory. Move the InfoClient.class and InfoClient$ButtonHandler.class classes to the codebase\ju\ch39\info\client directory and the InfoServer.class, InfoServerImpl.class, InfoServerImpl_Stub.class, and InfoServerImpl_Skel.class classes to the codebase\ju\ch39\info\server directory. Also, copy the info.htm file to your Web server's root directory. Your class files should now be organized as follows:
Substitute your Web server's root directory for WebRoot.
In order to execute the InfoServerImpl class, you'll need to include the codebase directory in the CLASSPATH of the computer that's hosting your Web server. Do this before going on to the next section. If you are running Windows 95, you'll have to restart your system for the new CLASSPATH to go into effect.
Hang on. We're almost there. Just a few more things to do and we can run the InfoServer application.
At this time, you should start your Web server if it isn't already up and running. I'm using Sun's Java Web Server as my Web server for this example. I configured it so that it listens for HTTP requests on port 80 instead of the default port 8080.
Start the remote registry program as follows:
start rmiregistry
On Windows 95 systems, the preceding command will create and open a blank DOS window. Just minimize the window and go on.
Now start up the server object as follows:
java ju.ch39.info.server.InfoServerImpl InfoServer is registered.
The server object will inform you that it has successfully registered itself with the remote registry program. That completes the server setup.
To run the application, you will need a Web browser that supports at least JDK 1.1. I'll use HotJava 1.1. You can also use Internet Explorer 4.0, Netscape Communicator 4.0, or appletviewer. From the same host or a different host, launch your browser and open up the URL http://your.server.com/info.htm, where your.server.com is the host name or IP address of the Web server where you installed the HTML and class files and ran the server object. If you are running the client and server on the same host, you can use localhost for your host name. Figure 39.1 shows the initial browser window. Now click the Update button a few times to receive news flashes from the remote object (refer to Figure 39.2).
The previous example showed how easy it is to use applets as the clients of distributed applications. After creating and compiling the required classes and starting the remote object, you just load the appropriate classes on your Web server. Users download the client applet as part of a Web page. No client installation is required.
The applet-based approach that you learned in the previous section works great for many distributed applications. However, there may be times when you want to run the client as an application and not as an applet. In this section, I'll show you an approach to distributing application clients that is almost as easy to use as the applet approach. This approach makes use of class loaders and security managers, and provides a practical introduction to these topics.
In many numerical applications such as simulations, applied genetic algorithms, and cryptography, it is important to have a good random number generator. All software-based random number generators are only pseudorandom and create repeating patterns after a time. Hardware-based random number generators, such as noisy diodes, have been developed that overcome the problems with software random number generators. Unfortunately, not everyone has immediate access to high-grade random number generators. That's where random number servers come in. You ask the server for a random number, and it gives you one.
The distributed application that you'll develop is a random number server. You'll be using the Math.random() function to generate random numbers. However, you can use this application as a basis for developing more advanced random number generators if you wish. The distributed application makes use of a random number client that is a window application program, as opposed to an applet. The client is maintained on a Web server, along with the random number server. A bootstrap program is developed that allows clients to remotely load the client. This bootstrap program is generic in nature and may be used with other distributed applications. The bootstrap program illustrates the use of class loaders and security managers within the context of distributed applications.
Listing 39.5 shows the RandomServer interface. This interface defines the getRandom() function that returns a double value between 0 and 1. Note that the interface is contained in the ju.ch39.random.server package.
package ju.ch39.random.server; import java.rmi.*; public interface RandomServer extends Remote { double getRandom() throws RemoteException; }
The RandomServerImpl class, shown in Listing 39.6, implements the RandomServer interface. The getRandom() method just returns a random number generated by the standard Math.random() method. To implement a high-grade random number server, you would replace this method with one that obtains its data from an external random number source. You would probably use a native method to accomplish this (see Chapter 53, "Native Methods").
The main() method installs a default RMISecurityManager object as the security manager and registers an instance of the RandomServerImpl class with the remote registry. It then displays a message to the console window informing you of its success.
package ju.ch39.random.server; import java.rmi.*; import java.rmi.server.*; public class RandomServerImpl extends UnicastRemoteObject implements RandomServer { public RandomServerImpl() throws RemoteException { super(); } public double getRandom() throws RemoteException { return Math.random(); } public static void main(String args[]){ System.setSecurityManager(new RMISecurityManager()); try { RandomServerImpl instance = new RandomServerImpl();
Naming.rebind("///RandomServer", instance);
System.out.println("RandomServer is registered."); } catch (Exception ex) { System.out.println(ex.toString()); } } }
The client application used with the random number server is shown in Listing 39.7. This application has an interesting twist. There's no main() method because the client's class file remains on the Web server and is loaded remotely (and executed) by a second bootstrap program. The bootstrap program is a generic program that can be used to remotely load distributed application clients. We'll cover the bootstrap program in the following section.
As a point of reference, Figure 39.3 shows the initial display of the RandomClient program. You click the New Random button and a new random number is displayed, as shown in Figure 39.4.
FIGURE 39.3. The initial display of RandomClient.
FIGURE 39.4. The random number returned by RandomServer.
Note that the host variable is assigned the IP address 204.115.182.233. You'll need to change this to your Web server's host name or IP address in order to run it with your Web server.
RandomClient implements the Runnable interface. This is so it can be executed as a separate thread by the bootstrap program. It implements an empty run() method in order to satisfy the Runnable interface. The main processing performed by RandomClient is creating the application window and handling the clicking of the New Random button.
During the creation of the application window, the connectToServer() method is invoked to obtain a reference to the RandomServer object. This reference is stored in the server variable.
The ButtonHandler class handles the clicking of the New Random button by invoking the getRandom() method of the RandomServer object and displaying the returned value as a text string.
package ju.ch39.random.client; import ju.ch39.random.server.*; import java.awt.*; import java.awt.event.*; import java.net.*; import java.rmi.*; import java.rmi.server.*; public class RandomClient extends Frame implements Runnable { String host="204.115.182.233"; TextField text = new TextField(50); Button newRandom = new Button("New Random"); RandomServer server; int screenWidth = 500; int screenHeight = 100; public void run(){ } public RandomClient() { super("RandomClient"); setup(); setSize(screenWidth,screenHeight); addWindowListener(new WindowEventHandler()); show(); } void setup() { setLayout(new FlowLayout(FlowLayout.LEFT)); newRandom.addActionListener(new ButtonHandler()); add(newRandom); add(text); connectToServer(); } void connectToServer() { try { server = (RandomServer) Naming.lookup("//"+host+"/RandomServer"); } catch (Exception ex) { text.setText(ex.toString()); } } class ButtonHandler implements ActionListener { public void actionPerformed(ActionEvent ev){ String s=ev.getActionCommand(); if("New Random".equals(s)){ try { double rand=server.getRandom(); text.setText(new Double(rand).toString()); } catch (Exception ex){ text.setText(ex.toString()); } } } } class WindowEventHandler extends WindowAdapter { public void windowClosing(WindowEvent e){ System.exit(0); } }
We finally get to the Bootstrap class, shown in Listing 39.8. This class implements the most interesting processing of the RandomServer application. However, before I get into the details of this processing, let me summarize its operation. You run the Bootstrap program on the client and specify the URL and name of a client application (that is, RandomClient). Bootstrap retrieves the client application from a remote Web server and executes it locally. The client application (running locally) uses RMI to invoke the methods of server objects (that is, RandomServer) executing on the Web server. The Bootstrap program solves the problem of maintaining and distributing client programs. The clients are maintained and stored on a central Web server. They can be upgraded and modified without having to be installed on users' computers. You only need to distribute the Bootstrap program, a small program that can be used to load and execute clients from any host.
The Bootstrap program is invoked with the -D option to set the java.rmi.server.codebase property to the URL where all classes should be loaded. The name of the class of the client program is also identified. The following is an example of the program's invocation:
java -Djava.rmi.server.codebase=http://host.com/codebase/ Bootstrap Client
The preceding command runs Bootstrap and sets the java.rmi.server.codebase property to http://host.com/codebase/. All classes are loaded from this URL. In particular, the Client class is loaded remotely and executed locally. Any classes that are loaded by Client are also loaded from the identified URL. If you can't picture how Bootstrap works, hang in there. When you actually run it, its function will be obvious.
Bootstrap is a simple console program. Its first few lines just display directions on how it is to be used. It is important to note that in the first line, it sets the security manager to an object of the BootstrapSecurityManager class. This class overrides RMISecurityManager and supports a more liberal policy for remotely loaded clients. BootstrapSecurityManager is covered in the next section.
The main processing performed by Bootstrap takes place within the try statement. It invokes the static loadClass() method of the RMIClassLoader class to load the class named as a command-line argument. This class is the name of the client application. The class is loaded from the URL specified by the java.rmi.server.codebase property.
The loaded class is assigned to the clientClass variable. An instance of the loaded class is then created and cast as a Runnable object. This creates a separate thread of execution. (That's why we implemented RandomClient as Runnable.) The run() method of the newly created thread is then invoked to cause the thread to be executed.
The three lines of code within the try statement accomplish quite a bit. They load a user-selected client application from a specified URL and execute it locally.
import java.rmi.*; import java.rmi.server.*; public class Bootstrap { public static void main(String args[]) { System.setSecurityManager(new BootstrapSecurityManager()); if(args.length!=1){ System.out.print("Usage: java -Djava.rmi.server.codebase=URL "); System.out.println("Bootstrap clientName"); System.out.println("\n Notes:"); System.out.print(" Use a URL of the form: "); System.out.println("http://host.com/codebase/"); System.out.print(" Substitute the name of the client's "); System.out.println("class for clientName"); System.out.println("\n Example:"); System.out.print(" java -Djava.rmi.server.codebase="); System.out.println("http://host.com/codebase/ Bootstrap Client"); System.exit(0); } System.out.println("Loading "+args[0]+" ..."); try { Class clientClass = RMIClassLoader.loadClass(args[0]); Runnable clientInstance = (Runnable) clientClass.newInstance(); clientInstance.run(); } catch (Exception ex) { System.out.println(ex.toString()); } } }
The BootstrapSecurityManager class is needed for Bootstrap to run without any security exceptions. The default RMISecurityManager is somewhat restrictive in the capabilities it permits for remotely loaded applications. It is similar to the security manager used with applets in this regard. How do we overcome these restrictions? By extending RMISecurityManager and overriding the methods that perform certain security checks.
Listing 39.9 shows the methods of RMISecurityManager that are overridden by BootstrapSecurityManager. These methods check a variety of operations to determine whether they are permissible under the RMI security policy. The RMISecurityManager methods with a void return value raise a SecurityException when an operation that violates the RMI security policy is detected. By overriding them in BootstrapSecurityManager, you prevent the SecurityException from occurring. The checkTopLevelWindow() method returns a boolean value indicating whether it is permissible for a remotely loaded class to open a top-level application window. By always returning a true value, you ensure that a top-level window can be created and opened by the remotely loaded client.
The methods that have been overridden by BootstrapSecurityManager are described in the RMISecurityManager class. You can override other methods in addition to these. The methods that were overridden were selected to allow clients, like RandomClient, to execute without a SecurityException.
import java.rmi.*; import java.io.*; public class BootstrapSecurityManager extends RMISecurityManager { public synchronized void checkAccess(Thread t){} public synchronized void checkAccess(ThreadGroup g){} public synchronized void checkExit(int status){} public synchronized void checkPropertiesAccess(){} public synchronized void checkAccept(String host,int port){} public synchronized void checkConnect(String host,int port){} public synchronized boolean checkTopLevelWindow(Object window){ return true; } public synchronized void checkPackageAccess(String pkg){} public void checkAwtEventQueueAccess(){} public synchronized void checkRead(FileDescriptor fd){} public synchronized void checkRead(String file){} }
You compile and install the RandomServer application using the following steps:
This may seem like a lot of work, but you'll only have to do all but the last step once. You'll need to copy the Bootstrap classes to any clients that want to load a client application remotely.
To run the RandomServer application, do the following:
The last step is performed as follows:
java -Djava.rmi.server.codebase=http://204.115.182.233/codebase/ ÂBootstrap ju.ch39.random.client.RandomClient
Substitute your Web server's host name or IP address for 204.115.182.233.
NOTE: If you intend to use the Bootstrap program, you may want to consider putting the long command line in a batch file.
When you run Bootstrap, it loads RandomClient from your Web server and executes it locally. Figure 39.3 shows its opening display. When you click the New Random button, RandomClient invokes the getRandom() method of the RandomServer object (executing on your Web server) and displays the value returned by getRandom().
In a large network running a multitude of server objects, it is sometimes difficult to keep track of which objects are running on which hosts. In this section, we'll develop a client application named Browser that enables you to browse a host's registry to see which remote objects it supports.
Listing 39.10 shows the Browser program. You compile it using javac Browser.java and run it using java Browser. Before you run Browser, start rmiregistry and run RandomServerImpl and InfoServerImpl. This will put two object names in your remote registry.
The Browser application's opening window is shown in Figure 39.5. Enter the name of the host running the remote registry and click the Browse host button. The text area in the center of the program window displays the objects that are maintained in the local registry. (See Figure 39.6.) You can also use this program to browse the remote registries of other hosts.
FIGURE 39.5. The Browser opening display.
Browser's ButtonEventHandler uses the static getRegistry() method of the LocateRegistry class to return a Registry object for a specified host. The list() method of the Registry interface returns an array that contains the names of all objects that are currently registered in the registry. These objects are displayed to the text area in the middle of the Browser window.
FIGURE 39.6. Browser displays the remote objects maintained by the remote registry on the specified host.
import java.awt.*; import java.awt.event.*; import java.rmi.registry.*; public class Browser extends Frame { TextField host = new TextField(30); TextArea objectNames = new TextArea(15,50); Button browse = new Button("Browse host"); int screenWidth = 500; int screenHeight = 400; public static void main(String args[]){ Browser app = new Browser(); } public Browser() { super("Browser"); setup(); setSize(screenWidth,screenHeight); addWindowListener(new WindowEventHandler()); show(); } void setup() { browse.addActionListener(new ButtonHandler()); Panel panel = new Panel(); panel.add(new Label("Host name: ")); panel.add(host); panel.add(browse); add("North",panel); add("Center",objectNames); }
class ButtonHandler implements ActionListener {
public void actionPerformed(ActionEvent ev){ String s=ev.getActionCommand(); if("Browse host".equals(s)){ try { Registry registry = LocateRegistry.getRegistry(host.getText()); String objectList[] = registry.list(); String objects=""; for(int i=0;i<objectList.length;++i){ objects+=objectList[i]+"\n"; } objectNames.setText(objects); } catch (Exception ex){ objectNames.setText(ex.toString()); } } } } class WindowEventHandler extends WindowAdapter { public void windowClosing(WindowEvent e){ System.exit(0); } } }
One of the new RMI features introduced in JDK 1.2 is the remote object activation framework. This framework allows you to activate remote objects remotely, deactivate the objects when they are not being used, and then reactivate the objects when needed. This capability is very important when you're developing large distributed systems consisting of many objects running on many computers. In these large distributed systems, it is impractical to have all objects running at the same time. Remote object activation allows you to build distributed systems that are more reliable and resource-efficient. Reliability is increased because remote object references are not transient references to object instances, but persistent references to objects that can be reactivated across multiple instances.
Remote activation is easy to understand and use, once you learn its terminology and work through an example. This section covers the terminology and introduces the concepts involved. The following section describes the mechanics of creating an activatable object. The section after that provides a remote activation example.
A remote object is active when it is instantiated within a Java Virtual Machine and exported for access via RMI. A remote object is inactive when it is not yet instantiated or exported. Activation is the process of transforming an inactive object into an active object. This involves instantiating the object within a JVM and restoring its state. An activatable object is an object that is capable of being activated.
RMI uses a form of remote activation that is referred to as lazy activation. Lazy activation does not mean that activation is slow or reticent. It means that a remote object is not activated until one of its methods has been invoked.
Lazy activation is implemented using a technique called faulting remote references. A remote reference to an object consists of the following two components:
A transient remote object reference.
A persistent object activation ID.
The transient remote object reference, or live reference, is a reference to an instance of a remote object. When a remote object is no longer active, the live reference is no longer valid. The persistent object activation ID is an object identifier that is valid whether the object is active or inactive. The activation ID provides a reference to the object's activator, which you'll learn about shortly.
When a client object invokes a method of an activatable remote object, the live reference is checked to determine whether it is valid. If it is not valid (that is, a faulting remote reference), the remote object's activation ID is used to activate the remote object and create a valid live reference. Activation of the remote object is performed using the activation protocol.
The activation protocol makes use of a special object, referred to as the activator. The activator is executed on a remote host as the result of running the rmid program that comes with JDK 1.2. The activator maintains a database of information that maps activatable objects to information that is needed to activate them, such as an object's class name and its code source (location). This information is referred to as an activation descriptor. The activator also maintains information on activation groups and instances of the JVM that are executing on the remote computer.
Each executing JVM is associated with an activation group. The activation group of a JVM is used to activate objects on that JVM. When the activator receives a faulting remote reference, it uses the object's activation ID (included in the reference) to look up the object's activation descriptor. The activation descriptor tells the activator what class to load, where to load it from (its code source), where to activate it (its activation group), and how to initialize the object. The activator checks for an executing JVM with the required activation group. If none is found, it creates one. The activator then requests the activation group to activate the object within the JVM. The activation group returns a valid live reference to the activated object, which is passed back to the client object making the method invocation.
NOTE: In order to use remote activation, you must first run the remote activation system daemon, rmid. You run it by typing start rmid in a console window.
In order to create an activatable object, you must do three things:
You must register the activation descriptor with the activator so the activator will have the information it needs to activate the object. The object's activation group uses the special constructors to create and activate (or reactivate) the object.
There are two ways to accomplish steps 1 and 2--the easy way and the hard way. The easy way is to have your object's class extend java.rmi.activation.Activatable. When you do this, registration and export are taken care of when your object is constructed. You still need to provide the special constructors, but you can take advantage of the constructors provided by Activatable. The hard way requires you to use the static register() and exportObject() methods of Activatable. The hard way isn't very difficult; it just involves more work. The advantage of not extending Activatable is that your class can appear somewhere else in the class hierarchy.
The special constructors that you need to implement have the following signatures:
You'll see examples of both of these constructors in the following sections.
Now that you've covered the terminology and mechanics of remote activation, you'll create and use an example of a remotely activatable object. This example will consist of the following classes and interfaces:
We'll examine the code of the client, server interface, and server before going on to the example.
The ActivationClient class is located in its own package, ju.ch39.activation.client, and consists of a single main() method. It displays the string ActivationClient to let you know that it is up and running, and then creates a remote reference to an ActivationServer object by using the lookup() method of the Naming class. I've hardcoded the IP address of my remote server (204.212.153.194) as an argument to the lookup() method. Substitute your server's host name or IP address for 204.212.153.194.
The main() method loops three times and displays the values returned by the getCount() and getState() methods that are invoked on the ActivationServer object. The getCount() method retrieves the value of a counter. The getState() method retrieves the activation state of the remote server object.
After the first for loop, the deactivate() method is invoked to cause the server object to deactivate itself. Another for loop follows that displays the results of getCount() and getState() after the server has been deactivated.
import ju.ch39.activation.server.*; import java.rmi.*; public class ActivationClient { public static void main(String args[]){ System.out.println("ActivationClient"); try { ActivationServer server = (ActivationServer) Naming.lookup("//204.212.153.194/ActivationServer"); for(int i=0;i<3;++i) { System.out.print(server.getCount()+" "); System.out.println(server.getState()); } server.deactivate(); for(int i=0;i<3;++i) { System.out.print(server.getCount()+" "); System.out.println(server.getState()); } } catch (Exception ex){ System.out.println(ex); } } }
The ActivationServer interface declares the getCount(), getState(), and deactivate() methods used by ActivationClient and implemented by ActivationServerImpl. It is part of the ju.ch39.activation.server package.
package ju.ch39.activation.server; import java.rmi.*; public interface ActivationServer extends Remote { int getCount() throws RemoteException; String getState() throws RemoteException; void deactivate() throws RemoteException; }
The ActivationServerImpl class implements the ActivationServer remote interface and extends the Activatable class. It defines the activationState variable to keep track of whether it has been initially created or has been reactivated. The count variable is used to implement a simple counter.
The ActivationServerImpl constructors illustrate the special constructors that are used with activatable objects. The first constructor is used for initially creating, registering, and exporting an object. It uses the superclass constructor to perform most of the processing. It then sets activationState to object created and binds itself to ActivationServer. The second constructor is used for activating and reactivating objects that have been created but have been deactivated. Like the first constructor, it uses the superclass constructor to perform most of the processing. It then sets activationState to object reactivated and binds itself to ActivationServer.
The getCount() and getState() methods return the value of count and activationState. The deactivate() method uses the getID() method to get its activation ID and passes the ID to the static inactive() method of the Activatable class. This causes the object to be deactivated. Once deactivated, an object must be reactivated in order to receive subsequent method invocations.
The main() method constructs a new ActivationServerImpl object and displays a message identifying that an ActivationServer object has been registered. I've put the required .class files on my Web server and used them as the code source. You should substitute your Web server's name within the code source's URL.
package ju.ch39.activation.server; import java.rmi.*; import java.rmi.server.*; import java.rmi.activation.*; import java.util.*; import java.net.*; import java.security.*; // Extend Activatable instead of UnicastRemoteObject public class ActivationServerImpl extends Activatable implements ActivationServer { String activationState = "undefined"; int count = 0; // Constructor used for creation public ActivationServerImpl(CodeSource source, MarshalledObject data,int port) throws RemoteException, ActivationException { super(source,data,port); activationState="object created"; try { Naming.rebind("///ActivationServer", this); }catch(Exception ex){ System.out.println(ex); } } // Constructor used for activation public ActivationServerImpl(ActivationID id, MarshalledObject data) throws RemoteException { super(id,0); activationState="object reactivated"; try { Naming.rebind("///ActivationServer", this); }catch(Exception ex){ System.out.println(ex); } } public int getCount() throws RemoteException { ++count; return count; } public String getState() throws RemoteException { String temp = activationState; activationState = "object active"; return temp; } public void deactivate() throws RemoteException { try { Activatable.inactive(this.getID()); }catch(ActivationException ex){ System.out.println(ex); } } public static void main(String args[]){ try { String className = "ju.ch39.activation.server.ActivationServerImpl"; URL url = new URL("http://204.212.153.194/codebase/"); CodeSource source = new CodeSource(url,null); new ActivationServerImpl(source,null,0); System.out.println("ActivationServer is registered."); } catch (Exception ex) { System.out.println(ex); } } }
To compile, install, and run the client and server objects, follow these steps:
When you run java ju.ch39.activation.server.ActivationServerImpl on the server machine, it displays the following message to let you know that it is up and running:
ActivationServer is registered.
When you run java ActivationClient on the client machine, it displays the following output:
ActivationClient 1 object created 2 object active 3 object active 1 object reactivated 2 object active 3 object active
You should follow the code in Listings 39.11 and 39.13 when examining this output. The first line indicates that ActivationClient is up and running. The second line indicates that the value of count is 1 and that activationState is object created. The next two lines indicate that count has been incremented and that the same object continues to be active. The fifth line occurs after ActivationClient invokes the deactivate() method of ActivationServer. It indicates that the remote object became inactive and was later reactivated. Note that when the object was reactivated, the value of count was reset. The last two lines show count being incremented while the remote object continues to be active.
In this chapter, you developed a few distributed applications that gave you some practical experience using RMI. You created an applet that uses RMI to connect to a server object, retrieve information, and display the information in a text area. You created a random number server and a client that accesses the server. You learned how to use class loaders and security managers to bootstrap the client from a remote host. You developed a program that contacts the remote registry of a host and obtains a list of the remote objects that it services. You also learned how to use remote activation. In the next chapter, you'll learn about object serialization, an important topic for RMI and JavaBeans.
© Copyright 1998, Macmillan Publishing. All rights reserved.