Chapter 5. Remote Objects

Periodically, the programming community starts thinking of “objects everywhere” as the solution to all its problems. The idea is to have a happy family of collaborating objects that can be located anywhere. These objects are, of course, supposed to communicate through standard protocols across a network. For example, you’ll have an object on the client where the user can fill in a request for data. The client object sends a message to an object on the server which it turns (somehow), gets the information, and sends it back to the client. Like most bandwagons in programming, this plan contains a fair amount of hype that can obscure the utility of the concept. This chapter:

  • Explains the models that make interobject communication possible

  • Explains situations where distributed objects can be useful

  • Shows you how to use remote objects and the associated remote method invocation (RMI) for communicating between two machines running Java Virtual Machines

Introduction to Remote Objects: The Roles of Client and Server

Let’s go back to that idea of locally collecting information on a client computer and sending the information across the Net to a server. We are supposing that a user on a local machine will fill out an information request form. The form gets sent to the vendor’s server, and the server processes the request and will, in turn, want to send back product information the client can view, as shown in Figure 5-1.

Transmitting objects between client and server

Figure 5-1. Transmitting objects between client and server

You have seen two ways to transmit information:

  • Use a socket connection to send byte streams between the customer and the vendor computers.

  • Use JDBC to make database queries and updates.

Both ways work well in certain situations. For example, a socket connection is great if you just need to send raw data across the Net. JDBC is useful if the information that you are sending fits into the relational database’s table model. Keep in mind that both of these models essentially send a stream of bytes across the Net. In both these cases, there is a significant coding hassle: the programmer has to come up with appropriate ways of coding the data and specifying the transmission protocols for sending the data. Of course, when you are connecting to a database with JDBC, all the details of communication protocols are taken care of in advance. But relational databases are not very effective for storing information that doesn’t fit into a “rows and columns” database structure. In particular, they are not very good at storing collections of objects of different types, such as a mixture of employees, managers, and contractors. Also, as you have seen, these kinds of heterogeneous object collections are very important in object-oriented programming.

What would go into a possible solution? Well, keeping in mind that object sending messages to one another is the central tenet of OOP, we could try to find a way to have objects lying on different machines send messages to each other. Let’s assume that the client object was written in Java so that it can theoretically run anywhere. For the server objects, there are two obvious possibilities:

  • The server object was not written in Java (either because it is a legacy object or because somebody hasn’t joined the appropriate bandwagon).

  • The server object was written in Java.

The first situation requires us to have a way for objects to talk to each other regardless of what language they were originally written in. If you think about it, you will agree with us that even the theoretical possibility of this is an amazing achievement. How can what is ultimately a sequence of bytes written in an arbitrary language, that we may have no knowledge of, tell us what services it offers, what messages it responds to? Of course, getting this to work in practice isn’t easy, but the idea is elegant. The “common object request broker architecture,” or CORBA standard, by the Object Management Group or OMG (www.omg.org) defines a common mechanism for data interchange and service discovery.

The fundamental idea is that we delegate the task of finding out this information and activating any services requested to a so-called Object Request Broker (or ORB). You can think of an ORB as a kind of universal translator for interobject communication. Objects don’t talk directly to each other. They always use an object broker to bargain between them. ORBs are located across the network, and it is important that they can communicate with each other. Most (but not all) ORBs follow the specification set up by the OMG for inter-ORB communication. This specification is called the Internet inter-ORB Protocol or IIOP.

NOTE

NOTE

Microsoft avoids the use of an explicit ORB in its common object model (COM). One corollary of this practice is that only objects built in a certain way on Windows 95 and NT platforms can talk to each other. (You can use many languages to build COM objects, however.) CORBA, on the other hand, is truly a cross-language and cross-platform architecture for interobject communication.

CORBA is completely language neutral. Client and server programs can be written in C++, Java, or any other language with a CORBA binding. You use an Interface Definition Language (or IDL) to specify the signatures of the messages and the types of the data your objects can send and understand. (IDL specifications look a lot like Java interfaces; in fact, you can think of them as defining interfaces that the communicating objects must support. One nice feature of this model is you can supply an IDL specification for an existing legacy object and then access its services through the ORB even if it was written long before the first ORB arrived.)

NOTE

NOTE

Sun wants Java objects to be able to talk to objects written in other languages, of course, so it supplies a Java-IDL interface and a tool called idltojava that compiles IDL files to Java source code. Information on the Java-IDL interface is, as we write this, at http://java.sun.com/products/jdk/idl/index.html.

There are quite a few people who believe that CORBAwill become very important very soon and that Java is an excellent language for implementing CORBA clients and servers. Upcoming versions of Netscape browsers and servers will ship with IIOP-compliant ORBs and a Java toolkit for CORBAprogramming. However, frankly speaking, the interoperability between ORBs has not been fully tested at this point. And there is little agreement on how to best write Java code for CORBA. Netscape uses a mechanism that is quite interesting, but it is completely different from the Java-IDL mechanism developed by Sun Microsystems. While CORBA may be interesting tomorrow, the Remote Method Invocation (RMI) mechanism, invented by JavaSoft for Java-to-Java communication, is available today, and it works. For that reason, we will discuss RMI in this chapter. RMI is useful only for communication between Java objects, and it does not currently use a standard transmission protocol such as IIOP. But the basic concepts of distributed computing are similar for RMI and CORBA, which means that you will benefit from understanding RMI, no matter what the eventual fate of RMI and CORBA is.

Remote Method Invocations

Java implements remote objects by supplying a transport layer that handles the data encoding and the transmission and call protocols. That way, you need not worry about managing streams of bytes. You can write code that sends messages to Java objects of any type. Thus, you are not limited to the rigid structure of a database as you would be if you stuck with JDBC.

There is another major benefit to remote objects in Java: Not only can you transport objects across a network, but you can invoke method calls on objects that reside on another computer without having to move those objects to the machine making the method call. Such method calls are called remote method invocations (RMI). For example, the client seeking product information can query a Warehouse object on the server. It calls a remote method, find, which has one parameter: the request form object. The find method returns an object to the client: the product information object. (See Figure 5-2.)

Invoking a remote method on a server object

Figure 5-2. Invoking a remote method on a server object

For remote method invocation (RMI), code on the client computer calls a method of a server object. It is important to remember that the client/server terminology applies only to a single method call. The computer running the Java code that calls the remote method is the client for that call, and the computer hosting the object that processes the call is the server for that call. It is entirely possible that the roles are reversed somewhere down the road. The server of a previous call can itself become the client when it invokes a remote method on an object residing on another computer.

Stubs and Skeletons

When client code wants to invoke a remote method on a remote object, it actually calls a regular Java method that is encapsulated in a surrogate object called a stub. The stub resides on the client machine, not on the server. The stub packages as a block of bytes the parameters used in the remote method. This packaging uses a device-independent encoding for each parameter. For example, numbers are always sent in big-endian format. Strings and objects are a little trickier since object references point to memory locations on the client. These memory locations will not make sense on the server. The stub, when it has an object reference to send across, uses the object serialization mechanism of Java to eliminate any explicit object references. (See Chapter 1.) The process of encoding the parameters into a format suitable for transporting them between objects running in different processes or across the Net is called parameter marshalling.

To sum up: the stub method on the client builds an information block that consists of:

  • An identifier of the remote object to be used

  • An operation number, describing the method to be called

  • The marshalled parameters

The stub then sends this information to the server. On the server side, a skeleton object makes sense out of the information contained in the packet and passes that information to the actual object executing the remote method. Specifically, the skeleton performs five actions for every remote method call:

  • It unmarshals the parameters.

  • It calls the desired method on the real remote object that lies on the server.

  • It captures the return value or exception of the call on the server.

  • It marshals that value.

  • It sends a package consisting of the value in the marshalled form back to the stub on the client.

The stub unmarshals the return value or exception from the server. This value becomes the return value of the remote method call. Or, if the remote method threw an exception, the stub rethrows it in the process space of the caller. Figure 5-3 shows the information flow of a remote method invocation.

Stub and skeleton objects

Figure 5-3. Stub and skeleton objects

This is obviously a complex process, but the good news is that it is completely automatic and, to a large extent, transparent for the Java programmer. Moreover, the designers of the Java remote object architecture tried hard to give remote objects the same “look and feel” as local objects. Nevertheless, there are important differences between local and remote objects, as you will see later in this chapter.

Remote objects are garbage collected automatically, just as local objects are. However, the current distributed collector uses reference counting and cannot detect cycles of unreferenced objects. Cycles must be explicitly broken by the programmer.

The syntax for a remote method call is the same as for a local call. If centralWarehouse is a stub object for a central warehouse object on a remote (currently, the client) machine and getQuantity is the method you want to invoke remotely, then a typical call looks like this:

centralWarehouse.getQuantity("SuperSucker 100 Vacuum Cleaner");

The client code always uses object variables whose type is an interface to access remote objects. For example, associated to this call would be an interface:

interface Warehouse 
{  public int getQuantity(String) 
      throws RemoteException; 
   . . . 
}

and an object declaration for a variable that will implement the interface:

Warehouse centralWarehouse;

Of course, interfaces are abstract entities that only spell out what methods can be called along with their signatures. Variables whose type is an interface must always be bound to an actual object of some type. In the case of remote objects, this is a stub class. The client program does not actually know the type of those objects. The stub classes and the associated objects are created automatically.

While the Java designers did a good job of hiding many details of remote method invocation from the Java programmer, a number of techniques and caveats still must be mastered. Those programming tasks are the topic of the rest of this chapter.

Dynamic Class Loading

When you pass a remote object to another Java program, either as a parameter or return value of a remote method, then that program must be able to deal with the associated stub object. That is, it must have the Java code for the stub class. The stub methods don’t do a lot of interesting work. They just marshal and unmarshal the parameters and then connect this information with the server. Of course, they do all this work transparently to the programmer.

Furthermore, the classes for parameters, return values, and exception objects may need to be loaded as well. This loading can be more complex than you might think. For example, you may declare a remote method with a certain return type that is known to the client, but the method actually returns an object of a derived class that is not known to the client. The class loader will then load that derived class.

While unglamorous, the stub classes must be available to the running client program. One obvious way to make these classes available is to put them on the local file system. If they aren’t there, then Java is quite willing to load them from another place. It does this by a process similar to what is used when running applets in a browser.

With an applet, a browser loads the applet class and checks the bytecodes for validity. The loading of the applet class from a remote location is the job of a class loader. The applet class loader is quite restrictive. It will load classes only from the same machine that served the Web page containing the applet. The stub class loader can be configured to permit more activities. For example, you can allow it to search for stub code on other network locations. This search is particularly useful for distributed Java programs where a number of processors that are cooperating to perform a difficult computation will all want to fetch the same stubs from a central location.

The class loader determines where the classes may be loaded from. The security manager determines what these classes can do when they run. You have seen in Chapter 10 of Volume 1 and Chapter 3 of this volume that the applet security manager won’t let classes read and write local files or make socket connections to third parties. The stub security manager is even more restrictive than the applet security manager. Since it governs the behavior only of stub code, the stub security manager prevents all activities except those that stubs must be able to carry out. This is a safety mechanism that protects the program from viruses in stub code. For specialized applications, Java programmers can substitute their own class loaders and security managers, but those provided by the RMI system suffice for normal usage. (See Chapter 8 for more on class loaders.)

Setting Up Remote Method Invocation

Running even the simplest remote object example requires quite a bit more setup than does running a standalone Java program or applet. You must run Java programs on both the server and client computers. The necessary object information must be separated into client-side interfaces and server-side implementations. There is also a special query mechanism that allows the client to locate objects on the server.

To get started with the actual coding, we walk through each of these requirements, using a simple example. In our first example, we generate a couple of objects of a type Product on the server computer. The client computer will run a Java program that locates and queries these objects.

NOTE

NOTE

You can try out this example on a single computer or on a pair of networked computers. The code on the CD is set up to run on a single computer. To run the server code remotely, set the URL of the server in the client code and recompile. We indicate where make these changes.

You also must distribute the class files between the client and the server. The server program classes, together with the interface, stub, and skeleton classes, must be on the server. The client program, together with the interface and stub classes, must be on the client.

Even if you run this code on a single computer, you must have network services available. In particular, be sure that you have TCP/IP running. This is always the case on a machine running Unix and will also be true on any machine that is permanently connected to the Internet. If your computer doesn’t have a network card, you can use the dialup networking feature under Windows 95 to set up a TCP/IP connection. (Consult the Windows 95 documentation to see how to set up the connection.)

Interfaces and Implementations

Your client program needs to manipulate server objects, but it doesn’t actually have copies of them. The objects themselves reside on the server. The client code must still know what it can do with those objects. Their capabilities are expressed in an interface that is shared between the client and server and so resides simultaneously on both machines.

interface Product // shared by client and server 
   extends Remote 
{  public String getDescription() throws RemoteException; 
}

Just as in this example, all interfaces for remote objects must extend the Remote interface defined in the java.rmi package. All the methods in those interfaces must also declare that they will throw a RemoteException. The reason for the declaration is that remote method calls are inherently less reliable than local calls—it is always possible that a remote call will fail. For example, the server or the network connection may be temporarily unavailable, or there may be a network problem. Your client code must be prepared to deal with these possibilities. For these reasons, Java forces you to catch the RemoteException with every remote method call and to specify the appropriate action to take when the call does not succeed. The client accesses the server object through a stub that implements this interface.

Product p = ...; 
   // see below how the client gets a stub 
   // reference to a remote object 
String d = p.getDescription(); 
System.out.println(d);

In the next section, you will see ways the client can obtain a reference to this kind of remote object.

Next, on the server side, you must implement the class that actually carries out the methods advertised in the remote interface.

public class ProductImpl // server 
   extends UnicastRemoteObject 
   implements Product 
{  public ProductImpl(String d) throws RemoteException 
      { descr = d; } 
   public String getDescription() 
   {  return "I am a " + descr + ". Buy me!"; 
   } 
   private String descr; 
}

This class has a single method, getDescription, that can be called from the remote client. It is a server class because it extends UnicastRemoteObject, which is a concrete Java class that makes objects remotely accessible.

NOTE

NOTE

When you use RMI (or any distributed object mechanism, for that matter), there is a somewhat bewildering set of classes to master. In this chapter, we use a uniform naming convention for all of our examples that, hopefully, makes it easier to recognize the purpose of each class. (See Table 5-1.) You can avoid the “server” classes by putting the server methods into the main method of the “implementation” classes.

Table 5-1. Naming Conventions for RMI classes

no suffix (e.g., Product )

A remote interface

Impl suffix (e.g., ProductImpl )

A server class implementing that interface

Server suffix (e.g., ProductServer )

A server program that creates server objects

Client suffix (e.g., ProductClient )

A client program that calls remote methods

_Stub suffix (e.g., ProductImpl_Stub )

A stub class that is automatically generated by the rmic program

_Skel suffix (e.g., ProductImpl_Skel )

A skeleton class that is automatically generated by the rmic program

Actually, all server classes must extend the class RemoteServer from the java.rmi.server package. But RemoteServer is an abstract class that defines only the basic mechanisms for the communication between server objects and their remote stubs. The UnicastRemoteObject class that comes with RMI extends the RemoteServer abstract class and is concrete—so you can use it without writing any code. The “path of least resistance” for a server class is to derive from UnicastRemoteObject, and all server classes in this chapter will do so. Figure 5-4 shows the inheritance relationship between these classes.

Inheritance diagram

Figure 5-4. Inheritance diagram

A UnicastRemoteObject object resides on a server. It must be alive when a service is requested, and reachable through the TCP/IP protocol. This is the class that we will be extending for all the server classes in this book and is the only server class available in the current version of the RMI package. Sun or third-party vendors may, in the future, design other classes for use by servers for RMI. For example, Sun is talking about a MulticastRemoteObject class for objects that are replicated over multiple servers. Other possibilities are for objects that are activated on demand or ones that can use other communications protocols, such as UDP.

Creating Server Objects

For a client to access a remote object that exists on the server, there must be a mechanism to obtain a remote reference that can access the remote object. There are a number of methods for the client code to gain access to a server object. The most common method is to call a remote method whose return value is a server object. When a server object is returned to the client as a method result, the RMI mechanism automatically sends back a remote reference, not the actual object. There is, however, a chicken-and-egg problem here. The first server object needs to be located some other way. That object typically has plenty of methods to return other objects. The Sun RMI library provides a bootstrap registry service to locate the first server object.

The server registers objects with the bootstrap registry service, and the client retrieves stubs to those objects. You register a server object by giving the bootstrap registry service a reference to the object and a name. The name is a string that is (hopefully) unique.

// server 
ProductImpl p1 = new ProductImpl("Blackwell Toaster"); 
Naming.bind("toaster", p1);

The client code gets a stub to access that server object by specifying the server name and the object name in a URL-style format:

// client 
String url = "rmi://"; 
   // change to rmi://www.yourserver.com/ 
   // if server runs remotely on www.yourserver.com 
Product c1 = (Product)Naming.lookup(url + "toaster");

NOTE

NOTE

Because it is notoriously difficult to keep names unique in a global registry, you should not use this technique as the general method for locating objects on the server. Instead, there should be relatively few named server objects registered with the bootstrap service. In our example, we temporarily violate this rule and register relatively trivial objects to show you the mechanics for registering and locating objects.

However, we aren’t quite ready to register any objects, yet. Because the bootstrap registry service must be available, it also must stay active for the duration. So, under Windows 95 or NT, you execute the statement

start rmiregistry

at a DOS prompt or from the Run dialog box. (The start command is a Windows command that starts a program in a new window.)

Under Unix, use:

rmiregistry &

TIP

You must set the desired class path before starting the registry service because new command windows inherit the class path of the command window in which they start. Otherwise, the registry won’t find the stub classes.

Next, generate skeletons and stubs for the ProductImpl class. Recall that skeletons and stubs are on the server-level and client-level classes and that RMI uses those classes to marshal (encode and send) the parameters and marshal the results of method calls across the network. The Java programmer never uses those classes directly. Moreover, they need not be written by hand. The rmic tool generates them automatically, as in the following example.

rmic ProductImpl

This call to the rmic tool generates two class files named ProductImpl_Skel.class and ProductImpl_Stub.class. If your class is in a package, you must call rmic with the full package name.

NOTE

NOTE

Remember to first compile the Java source file with javac before running rmic. If you are generating stubs and skeletons for a class in a package, you must give rmic the full package name.

All server and client programs that use RMI should install a security manager to control the code of any skeletons and stubs that are dynamically loaded from a network location. Java provides such a security manager, the RMISecurityManager. You install it with the instruction

System.setSecurityManager(new RMISecurityManager());

NOTE

NOTE

In this case, we do not actually need dynamic class loading. All the classes are available in the local file system. Nevertheless, Java insists that a security manager be in place. For applets, there is already a security manager that makes sure that the applet code does not do any harm. When you are dealing with applications, be aware there is no default security manager, so you always need to set a security manager when using RMI.

Example 5-1 shows a complete program that registers two Product objects under the names toaster and microwave.

Example 5-1. ProductServer.java

import java.rmi.*; 
import java.rmi.server.*; 
import sun.applet.*; 

public class ProductServer 
{  public static void main(String args[]) 
   {  System.setSecurityManager(new RMISecurityManager()); 

      try 
      {  ProductImpl p1 = new ProductImpl("Blackwell Toaster"); 
         ProductImpl p2 = new ProductImpl("ZapXpress Microwave 
            Oven"); 

         Naming.rebind("toaster", p1); 
         Naming.rebind("microwave", p2); 
      } 
      catch(Exception e) 
      {  System.out.println("Error: " + e); 
      } 
   } 
}

Once you compile this program, you run it as a separate process. Under Windows, use the command:

start java ProductServer

Under Unix, use the command

java ProductServer &

If you run the server program as

java ProductServer

then the program will never exit normally. This seems strange—after all, the program just creates two objects and registers them. Actually, the main function does exit immediately after registration, as you would expect. But, when you create an object of a class that extends UnicastRemoteObject, Java starts a separate thread that keeps the program alive indefinitely. Thus, the program stays around in order to allow clients to connect to it.

TIP

The Windows version of the JDK contains a command, javaw, that starts the Java interpreter as a separate Windows process and keeps it running. Some sources recommend that you use javaw, not start java, to run a Java session in the background in Windows for RMI. Doing so is not a good idea, for two reasons. Windows has no tool to kill a javaw background process—it does not show up in the task list. It turns out that you need to kill and restart the bootstrap registry service when you change the stub of a registered class. To kill a process that you started with the start command, all you have to do is click on the window and press CTRL+C.

There is another important reason to use the start command. When you run a server process by using javaw, messagessent to the output or error streams are discarded. In particular, they are not displayed anywhere. If you want to see output or error messages, use start instead. Then, error messages at least show up on the console. And trust us, you will want to see these messages. There are lots of things that can go wrong when you experiment with RMI. The most common error is probably that you forget to run rmic. Then, the server complains about missing stubs. If you use javaw, you won’t see that error message, and you’ll scratch your head wondering why the client can’t find the server objects.

Before writing the client program, let’s verify that we succeeded in registering the remote objects. The Naming class has a method list that returns a list of all currently registered names. Example 5-2 shows a simple program that lists the names in the registry.

Example 5-2. ShowBindings.java

import java.rmi.*; 
import java.rmi.server.*; 

public class ShowBindings 
{  public static void main(String[] args) 
   {  System.setSecurityManager(new RMISecurityManager()); 
      try 
      {  String[] bindings = Naming.list(""); 
         for (int i = 0; i < bindings.length; i++) 
            System.out.println(bindings[i]); 
      } 
      catch(Exception e) 
      {  System.out.println("Error: " + e); 
      } 
   } 
}

In our case, its output is

rmi:/toaster 
rmi:/microwave

The Client Side

Now, we can write the client program that asks each newly registered product object to print its description. See Example 5-3.

Example 5-3. ProductClient.java

import java.rmi.*; 
import java.rmi.server.*; 

public class ProductClient 
{  public static void main(String[] args) 
   {  System.setSecurityManager(new RMISecurityManager()); 
      String url = "rmi:///"; 
         // change to "rmi://www.yourserver.com/" 
         // when server runs on remote machine 
         // www.yourserver.com 
      try 
      {  Product c1 = (Product)Naming.lookup(url + "toaster"); 
         Product c2 = (Product)Naming.lookup(url + "microwave"); 
         System.out.println(c1.getDescription()); 
         System.out.println(c2.getDescription()); 
      } 
      catch(Exception e) 
      {  System.out.println("Error: " + e); 
      } 
      System.exit(0); 
   } 
}

You run this program on the client, in the usual way:

java ProductClient

The program simply prints

I am a Blackwell Toaster. Buy me! 
I am a ZapXpress Microwave Oven. Buy me!

This output doesn’t seem all that impressive, but consider what goes on behind the scenes when Java executes the call to the getDescription method. The client program has a reference to a stub object that it obtained from the lookup method. It calls the getDescription method, which sends a network message to the skeleton object on the server side. The skeleton object invokes the getDescription method on the ProductImpl object located on the server. That method computes a string. The string is returned to the skeleton, sent across the network, received by the stub, and returned as the result. See Figure 5-5.

Calling a remote method

Figure 5-5. Calling a remote method

Summary of Steps for Setting up RMI

Here is a summary of the steps you need to take to get remote method invocation working:

  1. Place the interface class extending Remote on the server and the client.

  2. Place the implementation class extending RemoteObject on the server.

  3. Generate stubs and skeletons on the server by running rmic. Copy the stubs to the client.

  4. Start the bootstrap registry service on the server.

  5. Start a program that creates and registers objects of the implementation class on the server.

  6. Run a program that looks up server objects and invokes remote methods on the client.

Parameter Passing in Remote Methods

You often want to pass parameters to remote objects. This section explains some of the techniques for doing so—along with some of the pitfalls.

Passing Nonremote Objects

When Java passes a remote object from the server to the client, the client receives a stub. Using the stub, it can manipulate the server object by invoking remote methods. The object, however, stays on the server. It is also possible to pass and return any objects via a remote method call, not just those that implement the Remote interface. For example, the getDescription method of the preceding section returned a String object. That string was created on the server and had to be transported to the client. Since String does not implement the Remote interface, the client cannot return a string stub object. Instead, the client gets a copy of the string. Then, after the call, the client has its own String object to work with. This means that there is no need for any further connection to any object on the server to deal with that string.

Whenever an object that is not a remote object needs to be transported from one Java Virtual Machine to another, the Java Virtual Machine makes a copy and sends that copy across the network connection. This technique is very different from parameter passing in a local method. When you pass objects into a local method or return them as method results, only object references are passed. However, object references are memory addresses of objects in the local Java Virtual Machine. This information is meaningless to a different Java Virtual Machine.

It is not difficult to imagine how a copy of a string can be transported across a network. Java can also make copies of more complex objects, provided they are serializable. RMI uses the serialization mechanism described in Chapter 1 to send objects across a network connection. This means that Java will only be able to copy the information in any classes that implement Serializable. The following program shows the copying of parameters and return values in action. This program is a simple application that lets a user shop for a gift. On the client, the user runs a program that gathers information about the gift recipient, in this case, age, sex, and hobbies (see Figure 5-6).

Obtaining product suggestions from the server

Figure 5-6. Obtaining product suggestions from the server

An object of type Customer is then sent to the server. Since Customer is not a remote object, a copy of the object is made on the server. The server program sends back a vector of products. The vector contains those products that match the customer profile, and it always contains that one item that will delight anyone, namely, a copy of the book Core Java. Again, Vector is not a remote class, so the vector is copied from the server back to its client. As described in Chapter 1, the serialization mechanism makes copies of all objects that are referenced inside a copied object. In our case, it makes a copy of all vector entries as well. We added an extra complexity: the entries are actually remote Product objects. Thus, the recipient gets a copy of the vector, filled with stub objects to the products on the server (see Figure 5-7).

Copying local parameter and result objects

Figure 5-7. Copying local parameter and result objects

To summarize, remote objects are passed across the network as stubs. Nonremote objects are copied. All of this is automatic and requires no programmer intervention.

Whenever code calls a remote method, the stub makes a package that contains copies of all parameter values and sends it to the server, using the object serialization mechanism to marshal the parameters. The server skeleton unmarshals them. Naturally, the process can be quite slow—especially when the parameter objects are large.

Let’s look at the complete program. First, we have the interfaces for the product and warehouse services, as shown in Examples 5-4 and 5-5.

Example 5-4. Product.java

import java.rmi.*; 
import java.awt.*; 

public interface Product 
   extends Remote 
{  String getDescription() 
      throws RemoteException; 

   static final int MALE = 1; 
   static final int FEMALE = 2; 
   static final int BOTH = MALE + FEMALE; 
}

Example 5-5. Warehouse.java

import java.rmi.*; 
import java.util.*; 

public interface Warehouse 
   extends Remote 
{  public Vector find(Customer c) 
      throws RemoteException; 
}

Example 5-6 shows the implementation for the product service. Products store a description, an age range, the gender targeted (male, female, or both), and the matching hobby. Note that this class implements the getDescription method advertised in the Product interface, and it also implements another method, match, which is not a part of that interface. The match method is an example of a local method, a method that can be called only from the local program, not remotely. Since the match method is local, it need not be prepared to throw a RemoteException.

Example 5-6. ProductImpl.java

import java.rmi.*; 
import java.rmi.server.*; 

public class ProductImpl 
   extends UnicastRemoteObject 
   implements Product 
{  public ProductImpl(String n, int s, int age1, int age2, String h) 
      throws RemoteException 
   {  name = n; 
      ageLow = age1; 
      ageHigh = age2; 
      sex = s; 
      hobby = h; 
   } 

   public boolean match(Customer c) // local method 
   {  if (c.getAge() < ageLow || c.getAge() > ageHigh) 
         return false; 
      if (!c.hasHobby(hobby)) return false; 
      if ((sex & c.getSex()) == 0) return false; 
      return true; 
   } 
   public String getDescription() 
      throws RemoteException 
   {  return "I am a " + name + ". Buy me!"; 
   } 

   private String name; 
   private int ageLow; 
   private int ageHigh; 
   private int sex; 
   private String hobby; 
}

Example 5-7. Customer.java

import java.io.*; 

public class Customer implements Serializable 
{  public Customer(int theAge, int theSex, String[] theHobbies) 
   {  age = theAge; 
      sex = theSex; 
      hobbies = theHobbies; 
   } 

   public int getAge() { return age; } 

   public int getSex() { return sex; } 

   public boolean hasHobby(String aHobby) 
   {  if (aHobby == "") return true; 
      for (int i = 0; i < hobbies.length; i++) 
         if (hobbies[i].equals(aHobby)) return true; 

      return false; 
   } 

   public void reset() 
   {  age = 0; 
      sex = 0; 
      hobbies = null; 
   } 

   public String toString() 
   {  String result = "Age: " + age + " Sex: "; 
      if (sex == Product.MALE) result += "Male"; 
      if (sex == Product.FEMALE) result += "Female"; 
      result += " Hobbies: "; 
      for (int i = 0; i < hobbies.length; i++) 
         result += hobbies[i] + " "; 
      return result; 
   } 

   private int age; 
   private int sex; 
   private String[] hobbies; 
}

Example 5-8 shows the implementation for the warehouse service. Like the ProductImpl class, the WarehouseImpl class has local and remote methods. The add method is local; it is used by the server to add products to the warehouse. The find method is remote; it is used to find items in the warehouse.

To illustrate that the Customer object is actually copied, the find method of the WarehouseImpl class actually clears the customer object it receives. When the remote method returns, the WarehouseClient displays the customer object that it sent to the server. As you will see, that object has not changed. The server cleared only its copy. In this case, the clear operation serves no useful purpose except to demonstrate that local objects are copied when they are passed as parameters.

Example 5-8. WarehouseImpl.java

import java.rmi.*; 
import java.util.*; 
import java.rmi.server.*; 

public class WarehouseImpl 
   extends UnicastRemoteObject 
   implements Warehouse 
{  public WarehouseImpl() 
      throws RemoteException 
   {  products = new Vector(); 
   } 

   public synchronized void add(ProductImpl p) // local method 
   {  products.addElement(p); 
   } 

   public synchronized Vector find(Customer c) 
      throws RemoteException 
   {  Vector result = new Vector(); 
      for (int i = 0; i < products.size(); i++) 
      {  ProductImpl p = (ProductImpl)products.elementAt(i); 
         if (p.match(c)) result.addElement(p); 
      } 
      result.addElement(new ProductImpl("Core Java Book", 
         0, 200, Product.BOTH, "")); 
      c.reset(); 
      return result; 
   } 

   private Vector products; 
}

In general, the methods of server classes such as ProductImpl and WarehouseImpl should be synchronized. Then, it is possible for multiple client stubs to make simultaneous calls to a server object, even if some of the methods change the state of the server. (See Chapter 2 for more details on synchronized methods.) In Example 5-8, we synchronize the methods of the WarehouseImpl class because it is conceivable that the local add and the remote find methods are called simultaneously. We don’t synchronize the methods of the ProductImpl class because the product server objects don’t change their state. Example 5-9 shows the server program that creates a warehouse object and registers it with the bootstrap registry service.

Example 5-9. WarehouseServer.java

import java.rmi.*; 
import java.rmi.server.*; 

public class WarehouseServer 
{  public static void main(String args[]) 
   {  System.setSecurityManager(new RMISecurityManager()); 
      try 
      {  WarehouseImpl w = new WarehouseImpl(); 
         fillWarehouse(w); 
         Naming.rebind("central_warehouse", w); 
      } 
      catch(Exception e) 
      {  System.out.println("Error: " + e); 
      } 
   } 

   public static void fillWarehouse(WarehouseImpl w) 
      throws RemoteException 
   {  w.add(new ProductImpl("Blackwell Toaster", Product.BOTH, 
         18, 200, "Household")); 
      w.add(new ProductImpl("ZapXpress Microwave Oven", 
         Product.BOTH, 
         18, 200, "Household")); 
      w.add(new ProductImpl("Jimbo After Shave", Product.MALE, 
         18, 200, "Beauty")); 
      w.add(new ProductImpl("Handy Hand Grenade", Product.MALE, 
         20, 60, "Gardening")); 
      w.add(new ProductImpl("DirtDigger Steam Shovel", 
         Product.MALE, 
         20, 60, "Gardening")); 
      w.add(new ProductImpl("U238 Weed Killer", Product.BOTH, 
         20, 200, "Gardening")); 
      w.add(new ProductImpl("Van Hope Cosmetic Set", 
         Product.FEMALE, 
         15, 45, "Beauty")); 
      w.add(new ProductImpl("Persistent Java Fragrance", 
         Product.FEMALE, 
         15, 45, "Beauty")); 
      w.add(new ProductImpl("Rabid Rodent Computer Mouse", 
         Product.BOTH, 
         6, 40, "Computers")); 
      w.add(new ProductImpl("Learn Bad Java Habits in 21 Days 
         Book", Product.BOTH, 
         20, 200, "Computers")); 
      w.add(new ProductImpl("My first Espresso Maker", 
         Product.FEMALE, 
         6, 10, "Household")); 
      w.add(new ProductImpl("JavaJungle Eau de Cologne", 
         Product.FEMALE, 
         20, 200, "Beauty")); 
      w.add(new ProductImpl("Fast/Wide SCSI Coffee Maker", 
         Product.MALE, 
        20, 50, "Computers")); 
      w.add(new ProductImpl("ClueLess Network Computer", 
         Product.BOTH, 
        6, 200, "Computers")); 
   } 
}

NOTE

NOTE

Remember that you must start the registry and the server program and keep both running before you start the client.

Example 5-10 shows the code for the client. When the user clicks on the Submit button, a new customer object is generated and passed to the remote find method. Then, the customer record is displayed in the text area (to prove that the clear call in the server did not affect it). Finally, the product descriptions of the returned products in the vector are added to the text area. Note that each getDescription call is again a remote method invocation.

Example 5-10. WarehouseClient.java

import java.awt.*; 
import java.awt.event.*; 
import java.rmi.*; 
import java.rmi.server.*; 
import java.util.*; 
import corejava.*; 

public class WarehouseClient extends CloseableFrame 
   implements ActionListener 
{  public WarehouseClient() 
   {  setLayout(new GridBagLayout()); 

      GridBagConstraints gbc = new GridBagConstraints(); 
      gbc.fill = GridBagConstraints.NONE; 
      gbc.weightx = 100; 
      gbc.weighty = 100; 
      add(new Label("Age:"), gbc, 0, 0, 1, 1); 
      add(age = new IntTextField(0, 4), gbc, 1, 0, 1, 1); 
      CheckboxGroup cbg = new CheckboxGroup(); 
      add(male = new Checkbox("Male", cbg, true), gbc, 0, 1, 1, 
         1); 
      add(female = new Checkbox("Female", cbg, true), gbc, 1, 1, 
         1, 1); 
      add(new Label("Hobbies"), gbc, 0, 2, 1, 1); 
      hobbies = new List(4, true); 
      hobbies.addItem("Gardening"); 
      hobbies.addItem("Beauty"); 
      hobbies.addItem("Computers"); 
      hobbies.addItem("Household"); 
      hobbies.addItem("Sports"); 
      add(hobbies, gbc, 1, 2, 1, 1); 
      Button submitButton = new Button("Submit"); 
      add(submitButton, gbc, 0, 3, 2, 1); 
      submitButton.addActionListener(this); 
      result = new TextArea(4, 40); 
      result.setEditable(false); 
      add(result, gbc, 0, 4, 2, 1); 
      System.setSecurityManager(new RMISecurityManager()); 
      String url = "rmi:///"; 
         // change to "rmi://www.yourserver.com/" 
         // when server runs on remote machine 
         // www.yourserver.com 
      try 
      (  centralWarehouse = 
         (Warehouse)Naming.lookup("central_warehouse"); 
      } 
      catch(Exception e) 
      {  System.out.println("Error: Can't connect to warehouse. " 
         + e); 
      } 
   } 

   private void add(Component c, GridBagConstraints gbc, 
      int x, int y, int w, int h) 
   {  gbc.gridx = x; 
      gbc.gridy = y; 
      gbc.gridwidth = w; 
      gbc.gridheight = h; 
      add(c, gbc); 
   } 

   public void actionPerformed(ActionEvent evt) 
   {  String arg = evt.getActionCommand(); 
      if (arg.equals("Submit")) 
      {  if (age.isValid()) 
         {  Customer c = new Customer(age.getValue(), 
               (male.getState() ? Product.MALE : 0) 
               + (female.getState() ? Product.FEMALE : 0), 
               hobbies.getSelectedItems()); 
            String t = c + "
"; 
            try 
            {  Vector result = centralWarehouse.find(c); 
               for (int i = 0; i < result.size(); i++) 
               {  Product p = (Product)result.elementAt(i); 
                  t += p.getDescription() + "
"; 
               } 
            } 
            catch(Exception e) 
            {  t = "Error: " + e; 
            } 
            result.setText(t); 
         } 
      } 
   } 
   public static void main(String[] args) 
   {  Frame f = new WarehouseClient(); 
      f.setSize(300, 300); 
      f.show(); 
   } 

   private Warehouse centralWarehouse; 
   private IntTextField age; 
   private Checkbox male; 
   private Checkbox female; 
   private List hobbies; 
   private TextArea result; 
}

Passing Remote Objects

Passing remote objects from the server to the client is simple. The client receives a stub object, then saves it in an object variable whose type is the same as the remote interface. The client can now access the actual object on the server through the variable. The client can copy this variable in its own local machine—all those copies are simply references to the same stub. It is important to note that only the remote interfaces can be accessed through the stub. A remote interface is any interface extending Remote. All local methods are inaccessible through the stub. (A local method is any method that is not defined in a remote interface.) Local methods can run only on the virtual machine containing the actual object.

Next, stubs are generated only from classes that implement a remote interface, and only the methods specified in the interfaces are provided in the stub classes. If a derived class doesn’t implement a remote interface but a base class does and an object of the derived class is passed to a remote method, only the base class methods are accessible. To understand this better, consider the following example. We derive a class BookImpl from ProductImpl :

class BookImpl extends ProductImpl 
{  public BookImpl(String title, String theISBN, 
      int sex, int age1, int age2, String hobby) 
   {  super(title + " Book", sex, age1, age2, hobby); 
      ISBN = theISBN; 
   } 
   public String getStockCode() { return ISBN; } 
   String ISBN; 
}

Now, suppose we pass a book object to a remote method, either as a parameter or as a return value. The recipient obtains a stub object. But that stub is not a book stub. Instead, it is a stub to the base class ProductImpl since only that class implements a remote interface (see Figure 5-8). Thus, in this case, the getStockCode method isn’t available remotely.

Only the ProductImpl methods are remote

Figure 5-8. Only the ProductImpl methods are remote

A remote class can implement multiple interfaces. For example, the BookImpl class can implement a second interface in addition to Product. Here, we define a remote interface StockUnit and have the BookImpl class implement it.

interface StockUnit extends Remote 
{  public String getStockCode() throws RemoteException; 
} 

class BookImpl extends ProductImpl implements StockUnit 
{  public BookImpl(String title, String theISBN, 
      int sex, int age1, int age2, String hobby) 
      throws RemoteException 
   {  super(title + " Book", sex, age1, age2, hobby); 
      ISBN = theISBN; 
   } 
   public String getStockCode() throws RemoteException 
   { return ISBN; } 

   private String ISBN; 
}

Figure 5-9 shows the inheritance diagram.

BookImpl has additional remote methods

Figure 5-9. BookImpl has additional remote methods

Now, when Java passes a book object to a remote method, the recipient obtains a stub that has access to the remote methods in both the Product and the StockUnit class. In fact, you can use the instanceof operator to find out whether a particular remote object implements an interface. Here is a typical situation where you will use this feature. Suppose you receive a remote object through a variable of type Product.

Vector result = centralWarehouse.find(c); 
for (int i = 0; i < result.size(); i++) 
{  Product p = (Product)result.elementAt(i); 
   . . . 
}

Now, the remote object may or may not be a book. We’d like to use instanceof to find out whether it is or not. But we can’t test

if (p instanceof BookImpl) // wrong 
{  BookImpl b = (BookImpl)p; 
   . . . 
}

The object p refers to a stub object, and BookImpl is the class of the server object. We could cast the stub object to a BookImpl_Stub,

if (p instanceof BookImpl_Stub) 
{  BookImpl_Stub b = (BookImpl_Stub)p; // not useful 
   . . . 
}

but that would not do us much good. The stubs are generated mechanically by the rmic program for internal use by the RMI mechanism, and clients should not have to think about them. Instead, we cast to the second interface:

if (p instanceof StockUnit) 
{  StockUnit s = (StockUnit)p; 
   String c = s.getStockCode(); 
   . . . 
}

This code tests whether the stub object to which p refers implements the StockUnit interface. If so, it calls the getStockCode remote method of that interface.

To summarize:

  • If an object belonging to a class that implements a remote interface is passed to a remote method, the remote method receives a stub object.

  • You can cast that stub object to any of the remote interfaces that the implementation class implements.

  • You can call all remote methods defined in those interfaces, but you cannot call any local methods through the stub.

Using Remote Objects in Hash Tables

As we saw in Chapter 11 of Volume 1, objects inserted in a hash table must override the equals and hashCode methods. Both methods are needed to find an object in a hash table. First, Java computes the hash code to find the appropriate bucket. Then, each object in that bucket is compared with the object to be matched, using the equals method. However, there is a problem when trying to compare remote objects. To find out if two remote objects have the same contents, the call to equals would need to contact the servers containing the objects and compare their contents. And that call could fail. But the equals method in the class Object is not declared to throw a RemoteException, whereas all methods in a remote interface must throw that exception. Since a subclass method cannot throw more exceptions than the superclass method it replaces, you cannot define an equals method in a remote interface. The same holds for hashCode.

Instead, you must rely on the redefinitions of the equals and hashCode methods in the RemoteObject class that is the base class for all stub and server objects. These methods do not look at the object contents, just at the location of the server objects. Two stubs that refer to the same server object are found to be equal by the equals method. Two stubs that refer to different server objects are never equal, even if those objects have identical contents. Similarly, the hash code is computed only from the object identifier. Stubs that refer to different server objects will likely have different hash codes, even if the server objects have identical contents.

This limitation refers only to stubs. You can redefine equals or hashCode for the server object classes. Those methods are called when you are inserting server objects in a hash table on the server, but they are never called when you are comparing or hashing stubs. To clarify the difference between client and server behavior, look at the inheritance diagram in Figure 5-10.

Inheritance of equals and hashCode methods

Figure 5-10. Inheritance of equals and hashCode methods

The RemoteObject class is the base for both stub and server classes. On the stub side, you cannot override the equals and hashCode methods because the stubs are mechanically generated. On the server side, you can override the methods for the implementation classes, but they are only used locally on the server. If you do override these methods, implementation and stub objects are no longer considered identical.

To summarize: You can use stub objects in hash tables, but you must remember that equality testing and hashing do not take the contents of the remote objects into account.

Cloning Remote Objects

Stubs do not have a clone method, so you cannot clone a remote object by invoking clone on the stub. The reason is again somewhat technical. If clone were to make a remote call to tell the server to clone the implementation object, then the clone method would need to throw a RemoteException. But the clone method in the Object superclass promised never to throw any exception except CloneNotSupportedException. That is the same limitation that you encountered in the previous section, when you saw that equals and hashCode don’t look up the remote object value at all but just compare stub references. But it makes no sense for clone to make another clone of a stub—if you wanted to have another reference to the remote object, you could just copy the stub variable. Therefore, clone is simply not defined for stubs.

If you want to clone a remote object, you must write another method, say, remoteClone. Place it into the interface that defines the remote object services. Of course, that method may throw a RemoteException. In the implementation class, simply define remoteClone to call clone and return the cloned implementation object.

interface Product extends Remote 
{  public Object remoteClone() 
   throws RemoteException, CloneNotSupportedException; 
   . . . 
} 

class ProductImpl extends UnicastRemoteObject 
   implements Product 
{  public Object remoteClone() 
   throws CloneNotSupportedException 
   { return clone(); } 
   . . . 
}

Inappropriate Remote Parameters

Suppose we enhance our shopping application by having the application show a picture of each gift. Can we simply add the remote method

void paint(Graphics g) throws RemoteException

to the Product interface? Unfortunately, this code cannot work, and it is important you understand why. The problem is that the Graphics class does not implement remote interfaces. Therefore, a copy of an object of type Graphics would need to be passed to the remote object, and you can’t do this. Why? Well, Graphics is an abstract class, and Graphics objects are returned via a call to the getGraphics method of the Component class. This call, in turn, can happen only when you have some subclass that implements a graphics context on a particular platform. Those objects, in turn, need to interact with the native graphics code, and to do so, they must store pointers to the memory blocks that are needed by the native graphics methods. Java, of course, has no pointers, so this information is stored as integers in the graphics object and is only cast back to pointers in the native peer methods. Now, first of all, the target machine may be a different platform. For example, if the client runs Windows and the server runs X11, then the server does not have the native methods available to render Windows graphics. But even if the server and the client have the same graphics system, the pointer values would not be valid on the server. Therefore, it makes no sense to copy a graphics object. For that reason, the Graphics class is not serializeable and so cannot be sent via RMI.

Instead, if the server wants to send an image to the client, it has to come up with some other mechanism for transporting the data across the network. As it turns out, this data transport is actually difficult to do for images. The Image class is just as device dependent as the Graphics class. We could send the image data as a sequence of bytes in JPEG format, but there is no method in the AWT package to turn a block of JPEG data into an image. (Currently, this can be done only by using unpublished classes in the sun.awt.image package.) Another alternative is to send an array of integers representing the pixels via a call to the PixelGrabber class and do the translation yourself—you will see this technique in Chapter 7. In the next section, we show how to solve this problem in a more mundane way: by sending a URL to the client and using a method of the Applet class that can read an image from a URL.

Using RMI with Applets

There are a number of special concerns when running RMI with applets. Applets have their own security manager since they run inside a browser. Thus, we do not use the RMISecurityManager on the client side.

We must take care where to place the stub and server files. Consider a browser that opens a Web page with an APPLET tag. The browser loads the class file referenced in that tag and all other class files as they are needed during execution. The class files are loaded from the same host that contains the Web page. Because of applet security restrictions, the applet can make network connections only to its originating host. Therefore, the server objects must reside on the same host as the Web page. That is, the same host must store

  • Web page

  • Applet code

  • Stub code

  • Skeletons and server objects

  • Bootstrap registry

Here is a sample applet that further extends our shopping program. Just like the preceding application, the applet gets the customer information and then recommends matching purchases. When the user clicks on one of the suggestions, the applet displays an image of the item. As we mentioned previously, it is not easy to send an image from the server to the client because images are stored in a format that depends on the local graphics system. Instead, the server simply sends the client a string with the image file name, and we use the getImage method of the Applet class to obtain the image (see Figure 5-11).

The warehouse applet

Figure 5-11. The warehouse applet

Here is how you must distribute the code for this kind of situation:

  • java.rmi.registry.RegistryImpl —. Anywhere on host; the registry must be running before applet starts

  • WarehouseServer —. Anywhere on host; must be running before the applet starts

  • WarehouseImpl —. Skeletons; can be anywhere on the host as long as WarehouseServer can find it

  • WarehouseApplet —. Directory referenced in APPLET tag

  • Stubs —. Must be in the same directory as WarehouseApplet

Note

Stubs —

Do not try this code with a browser that does not support the RMI features of Java 1.1. The code will not work and may even crash browsers that are not ready for RMI applets. The applet works fine with the JDK applet viewer.

Example 5-11 shows the code for the applet. Note that the applet does not install a security manager and that it looks for the bootstrap registry on the same host that contains the applet.

Example 5-11. WarehouseApplet.java

import java.awt.*; 
import java.awt.event.*; 
import java.applet.*; 
import java.rmi.*; 
import java.rmi.server.*; 
import java.util.*; 
import corejava.*; 

public class WarehouseApplet extends Applet 
   implements ActionListener, ItemListener 
{  public void init() 
   {  setLayout(new GridBagLayout()); 

      GridBagConstraints gbc = new GridBagConstraints(); 
      gbc.fill = GridBagConstraints.NONE; 
      gbc.weightx = 100; 
      gbc.weighty = 100; 
      add(new Label("Age:"), gbc, 0, 0, 1, 1); 
      add(age = new IntTextField(0, 4), gbc, 1, 0, 1, 1); 
      CheckboxGroup cbg = new CheckboxGroup(); 
      add(male = new Checkbox("Male", cbg, true), gbc, 0, 1, 1, 
         1); 
      add(female = new Checkbox("Female", cbg, true), gbc, 1, 1, 
         1, 1); 
      add(new Label("Hobbies"), gbc, 0, 2, 1, 1); 
      hobbies = new List(4, true); 
      hobbies.addItem("Gardening"); 
      hobbies.addItem("Beauty"); 
      hobbies.addItem("Computers"); 
      hobbies.addItem("Household"); 
      hobbies.addItem("Sports"); 
      add(hobbies, gbc, 1, 2, 1, 1); 
      Button submitButton = new Button("Submit"); 
      add(submitButton, gbc, 0, 3, 2, 1); 
      submitButton.addActionListener(this); 
      descriptions = new List(4, false); 
      gbc.fill = GridBagConstraints.HORIZONTAL; 
      add(descriptions, gbc, 0, 4, 2, 1); 
      descriptions.addItemListener(this); 
      gbc.fill = GridBagConstraints.NONE; 
      canvas = new Canvas(); 
      canvas.setSize(150, 150); 
      add(canvas, gbc, 0, 5, 2, 1); 

      String url = getCodeBase().getHost(); 
      if (url.equals("default")) url = ""; 
      url = "rmi://" + url; 
      try 
      { centralWarehouse = (Warehouse)Naming.lookup(url + 
            "/central_warehouse"); 
      } 
      catch(Exception e) 
      {  showStatus("Error: Can't connect to warehouse. " + e); 
      } 
   } 

   private void add(Component c, GridBagConstraints gbc, 
      int x, int y, int w, int h) 
   {  gbc.gridx = x; 
      gbc.gridy = y; 
      gbc.gridwidth = w; 
      gbc.gridheight = h; 
      add(c, gbc); 
   } 

   public void actionPerformed(ActionEvent evt) 
   {  if (evt.getActionCommand().equals("Submit")) 
      {  if (age.isValid()) 
         {  Customer c = new Customer(age.getValue(), 
               (male.getState() ? Product.MALE : 0) 
               + (female.getState() ? Product.FEMALE : 0), 
               hobbies.getSelectedItems()); 
            try 
            {  products = centralWarehouse.find(c); 
               descriptions.removeAll(); 
               for (int i = 0; i < products.size(); i++) 
               {  Product p = (Product)products.elementAt(i); 
                  descriptions.addItem(p.getDescription()); 
               } 
            } 
            catch(Exception e) 
            {  System.out.println("Error: " + e); 
            } 
         } 
      } 
   } 

   public void itemStateChanged(ItemEvent evt) 
   {  if (evt.getStateChange() == ItemEvent.SELECTED) 
      {  int index = descriptions.getSelectedIndex(); 
         if (index < 0) return; 
         try 
         {  Product p = (Product)products.elementAt(index); 
            productImage = getImage(getCodeBase(), 
               p.getImageFile()); 
            repaint(); 
         } 
         catch(Exception e) 
         {  System.out.println("Error: " + e); 
         } 
      } 
   } 

   public void paint(Graphics g) 
   {  if (productImage == null) return; 
      Graphics cg = canvas.getGraphics(); 
      cg.clearRect(0, 0, canvas.getSize().width, 
         canvas.getSize().height); 
      cg.drawImage(productImage, 0, 0, this); 
      cg.dispose(); 
   } 

   private Warehouse centralWarehouse; 
   private IntTextField age; 
   private Checkbox male; 
   private Checkbox female; 
   private List hobbies; 
   private List descriptions; 
   private Vector products; 
   private Canvas canvas; 
   private Image productImage; 
}

The server sets the file names when it populates the database.

Example 5-12. WarehouseServer.java

import java.rmi.*; 
import java.rmi.server.*; 

public class WarehouseServer 
{  public static void main(String args[]) 
   {  System.setSecurityManager(new RMISecurityManager()); 
      try 
      {  WarehouseImpl w = new WarehouseImpl(); 
         fillWarehouse(w); 
         Naming.rebind("central_warehouse", w); 
      } 
      catch(Exception e) 
      {  System.out.println("Error: " + e); 
      } 
   } 

   public static void fillWarehouse(WarehouseImpl w) 
      throws RemoteException 
   {  w.add(new ProductImpl("Blackwell Toaster", Product.BOTH, 
         18, 200, "Household", "toaster.jpg")); 
      w.add(new ProductImpl("Jimbo After Shave", Product.MALE, 
         18, 200, "Beauty", "shave.jpg")); 
      w.add(new ProductImpl("U238 Weed Killer", Product.BOTH, 
         20, 200, "Gardening", "weed.jpg")); 
      w.add(new ProductImpl("Rabid Rodent Computer Mouse", 
         Product.BOTH, 
         6, 40, "Computers", "rodent.jpg")); 
      w.add(new ProductImpl("Learn Bad Java Habits in 21 Days 
         Book", Product.BOTH, 
         20, 200, "Computers", "book.jpg")); 
      w.add(new ProductImpl("JavaJungle Eau de Cologne", 
         Product.FEMALE, 
         20, 200, "Beauty", "cologne.jpg")); 
      w.add(new ProductImpl("Fast/Wide SCSI Coffee Maker", 
         Product.MALE, 
        20, 50, "Computers", "coffee.jpg")); 
      w.add(new ProductImpl("ClueLess Network Computer", 
         Product.BOTH, 
        6, 200, "Computers", "computer.jpg")); 
      w.add(new ProductImpl("Digging Dinosaur", Product.BOTH, 
        6, 200, "Gardening", "dino.jpg")); 
      w.add(new ProductImpl("Fantastic Fan", Product.BOTH, 
        6, 200, "Household", "fan.jpg")); 
      w.add(new ProductImpl("Japanese Cat", Product.BOTH, 
        6, 200, "Gardening", "cat.jpg")); 
      w.add(new ProductImpl("Ms. Frizzle Curling Iron", 
         Product.FEMALE, 
        6, 200, "Beauty", "curl.jpg")); 
   } 
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.148.149