Parameter Passing in Remote Methods


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

Passing Nonremote Objects

When a remote object is passed 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 with 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. Because String does not implement the Remote interface, the server 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. The RMI mechanism can also make copies of more complex objects, provided they are serializable. RMI uses the serialization mechanism described in Volume 1, Chapter 12 to send objects across a network connection. This means that only the information in any classes that implement the Serializable interface can be copied.

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-7).

Figure 5-7. Obtaining product suggestions from the server


NOTE

Figure 5-7 has a curious banner "Java Applet Window". This is a consequence of running the program with a security manager. This warning banner is provided to defend against "phishing" applets. A hostile applet might pop up a window, prompt for a password or credit card number, and then send the information back to its host. To turn off the warning, add the following lines to the client.policy file:

 permission java.awt.AWTPermission    "showWindowWithoutWarningBanner"; 


An object of type Customer is then sent to the server. Because Customer is not a remote object, a copy of the object is made on the server. The server program sends back an array list of products. The array list 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, ArrayList is not a remote class, so the array list is copied from the server back to its client. As described in Volume 1, Chapter 12, the serialization mechanism makes copies of all objects that are referenced inside a copied object. In our case, it makes a copy of all array list entries as well. We added an extra complexity: The entries are actually remote Product objects. Thus, the recipient gets a copy of the array list, filled with stub objects to the products on the server (see Figure 5-8).

Figure 5-8. 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 unmarshals them. Naturally, the process can be quite slowespecially 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 Example 5-6 and Example 5-7.

Example 5-6. Product.java
  1. import java.rmi.*;  2.  3. /**  4.    The interface for remote product objects.  5. */  6. public interface Product extends Remote  7. {  8.    /**  9.       Gets the description of this product. 10.       @return the product description 11.    */ 12.    String getDescription() throws RemoteException; 13. } 

Example 5-7. Warehouse.java
  1. import java.rmi.*;  2. import java.util.*;  3.  4. /**  5.    The remote interface for a warehouse with products.  6. */  7. public interface Warehouse extends Remote  8. {  9.    /** 10.       Gets products that are good matches for a customer. 11.       @param c the customer to match 12.       @return an array list of matching products 13.    */ 14.    ArrayList<Product> find(Customer c) throws RemoteException; 15. } 

Example 5-8 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. Because the match method is local, it need not be prepared to throw a RemoteException.

Example 5-9 contains the code for the Customer class. Note once again that Customer is not a remote classnone of its methods can be executed remotely. However, the class is serializable. Therefore, objects of this class can be transported from one virtual machine to another.

Examples 5-10 and 5-11 show the interface and 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 by the client to find items in the warehouse.

To illustrate that the Customer object is actually copied, the find method of the WarehouseImpl class 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 reset operation serves no useful purpose except to demonstrate that local objects are copied when they are passed as parameters.

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. In Example 5-11, we use a ReadWriteLock in the WarehouseImpl class because it is conceivable that the local add and the remote find methods are called simultaneously. Example 5-12 shows the server program that creates a warehouse object and registers it with the bootstrap registry service.

NOTE

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


Example 5-13 shows the code for the client. When the user clicks the Submit button, a new customer object is generated and passed to the remote find method. The customer record is then displayed in the text area (to prove that the reset call in the server did not affect it). Finally, the product descriptions of the returned products in the array list are added to the text area. Note that each getdescription call is again a remote method invocation. That would not be a good design in practiceyou would normally pass small objects such as product descriptions by value. However, we want to demonstrate that a remote object is automatically replaced by a stub during marshalling.

TIP

If you start the server with

 java -Djava.rmi.server.logCalls=true WarehouseServer & 

then the server logs all remote method calls on its console. Try ityou'll get a good impression of the RMI traffic.


Example 5-8. ProductImpl.java
  1. import java.rmi.*;  2. import java.rmi.server.*;  3.  4. /**  5.    This is the implementation class for the remote product  6.    objects.  7. */  8. public class ProductImpl  9.    extends UnicastRemoteObject 10.    implements Product 11. { 12.    /** 13.       Constructs a product implementation 14.       @param n the product name 15.    */ 16.    public ProductImpl(String n) throws RemoteException 17.    { 18.       name = n; 19.    } 20. 21.    public String getDescription() throws RemoteException 22.    { 23.       return "I am a " + name + ". Buy me!"; 24.    } 25. 26.    private String name; 27. } 

Example 5-9. Customer.java
  1. import java.io.*;  2.  3. /**  4.    Description of a customer. Note that customer objects are not  5.    remote--the class does not implement a remote interface.  6. */  7. public class Customer implements Serializable  8. {  9.    /** 10.       Constructs a customer. 11.       @param theAge the customer's age 12.       @param theSex the customer's sex (MALE or FEMALE) 13.       @param theHobbies the customer's hobbies 14.    */ 15.    public Customer(int theAge, int theSex, String[] theHobbies) 16.    { 17.       age = theAge; 18.       sex = theSex; 19.       hobbies = theHobbies; 20.    } 21. 22.    /** 23.       Gets the customer's age. 24.       @return the age 25.    */ 26.    public int getAge() { return age; } 27. 28.    /** 29.       Gets the customer's sex 30.       @return MALE or FEMALE 31.    */ 32.    public int getSex() { return sex; } 33. 34.    /** 35.       Tests whether this customer has a particular hobby. 36.       @param aHobby the hobby to test 37.       @return true if this customer has the hobby 38.    */ 39.    public boolean hasHobby(String aHobby) 40.    { 41.       if (aHobby == "") return true; 42.       for (int i = 0; i < hobbies.length; i++) 43.          if (hobbies[i].equals(aHobby)) return true; 44. 45.       return false; 46.    } 47. 48.    /** 49.       Resets this customer record to default values. 50.    */ 51.    public void reset() 52.    { 53.       age = 0; 54.       sex = 0; 55.       hobbies = null; 56.    } 57. 58.    public String toString() 59.    { 60.       String result = "Age: " + age + ", Sex: "; 61.       if (sex == Product.MALE) result += "Male"; 62.       else if (sex == Product.FEMALE) result += "Female"; 63.       else result += "Male or Female"; 64.       result += ", Hobbies:"; 65.       for (int i = 0; i < hobbies.length; i++) 66.          result += " " + hobbies[i]; 67.       return result; 68.    } 69. 70.    private int age; 71.    private int sex; 72.    private String[] hobbies; 73. } 

Example 5-10. Warehouse.java
  1. import java.rmi.*;  2. import java.util.*;  3.  4. /**  5.    The remote interface for a warehouse with products.  6. */  7. public interface Warehouse extends Remote  8. {  9.    /** 10.       Gets products that are good matches for a customer. 11.       @param c the customer to match 12.       @return an array list of matching products 13.    */ 14.    ArrayList<Product> find(Customer c) throws RemoteException; 15. } 

Example 5-11. WarehouseImpl.java
  1. import java.io.*;  2. import java.rmi.*;  3. import java.util.*;  4. import java.rmi.server.*;  5. import java.util.*;  6. import java.util.concurrent.locks.*;  7.  8. /**  9.    This class is the implementation for the remote 10.    Warehouse interface. 11. */ 12. public class WarehouseImpl 13.    extends UnicastRemoteObject 14.    implements Warehouse 15. { 16.    /** 17.       Constructs a warehouse implementation. 18.    */ 19.    public WarehouseImpl() 20.       throws RemoteException 21.    { 22.       products = new ArrayList<ProductImpl>(); 23.       add(new ProductImpl("Core Java Book", 0, 200, Product.BOTH, "Computers")); 24.    } 25. 26.    /** 27.       Add a product to the warehouse. Note that this is a local method. 28.       @param p the product to add 29.    */ 30.    public void add(ProductImpl p) 31.    { 32.       Lock wlock = rwlock.writeLock(); 33.       wlock.lock(); 34.       try 35.       { 36.          products.add(p); 37.       } 38.       finally 39.       { 40.          wlock.unlock(); 41.       } 42.    } 43. 44.    public ArrayList<Product> find(Customer c) 45.       throws RemoteException 46.    { 47.       Lock rlock = rwlock.readLock(); 48.       rlock.lock(); 49.       try 50.       { 51.          ArrayList<Product> result = new ArrayList<Product>(); 52.          // add all matching products 53.          for (ProductImpl p : products) 54.          { 55.             if (p.match(c)) result.add(p); 56.          } 57.          // add the product that is a good match for everyone, a copy of Core Java 58.          if (!result.contains(products.get(0))) 59.             result.add(products.get(0)); 60. 61.          // we reset c just to show that c is a copy of the client object 62.          c.reset(); 63.          return result; 64.       } 65.       finally 66.       { 67.          rlock.unlock(); 68.       } 69.    } 70. 71.    private ArrayList<ProductImpl> products; 72.    private ReadWriteLock rwlock = new ReentrantReadWriteLock(); 73. } 

Example 5-12. WarehouseServer.java

[View full width]

  1. import java.rmi.*;  2. import java.rmi.server.*;  3. import javax.naming.*;  4.  5. /**  6.    This server program instantiates a remote warehouse  7.    object, registers it with the naming service, and waits  8.    for clients to invoke methods.  9. */ 10. public class WarehouseServer 11. { 12.    public static void main(String[] args) 13.    { 14.       try 15.       { 16.          System.out.println("Constructing server implementations..."); 17.          WarehouseImpl w = new WarehouseImpl(); 18.          w.add(new ProductImpl("Blackwell Toaster", Product.BOTH, 18, 200, "Household")); 19.          w.add(new ProductImpl("ZapXpress Microwave Oven", Product.BOTH, 18, 200,  "Household")); 20.          w.add(new ProductImpl("DirtDigger Steam Shovel", Product.MALE, 20, 60,  "Gardening")); 21.          w.add(new ProductImpl("U238 Weed Killer", Product.BOTH, 20, 200, "Gardening")); 22.          w.add(new ProductImpl("Persistent Java Fragrance", Product.FEMALE, 15, 45,  "Beauty")); 23.          w.add(new ProductImpl("Rabid Rodent Computer Mouse", Product.BOTH, 6, 40,  "Computers")); 24.          w.add(new ProductImpl("My first Espresso Maker", Product.FEMALE, 6, 10,  "Household")); 25.          w.add(new ProductImpl("JavaJungle Eau de Cologne", Product.MALE, 15, 45,  "Beauty")); 26.          w.add(new ProductImpl("FireWire Espresso Maker", Product.BOTH, 20, 50,  "Computers")); 27.          w.add(new ProductImpl("Learn Bad Java Habits in 21 Days Book", Product.BOTH,  20, 200, 28.             "Computers")); 29. 30.          System.out.println("Binding server implementations to registry..."); 31.          Context namingContext = new InitialContext(); 32.          namingContext.bind("rmi:central_warehouse", w); 33. 34.          System.out.println("Waiting for invocations from clients..."); 35.       } 36.       catch (Exception e) 37.       { 38.          e.printStackTrace(); 39.       } 40.    } 41. } 

Example 5-13. WarehouseClient.java

[View full width]

   1. import java.awt.*;   2. import java.awt.event.*;   3. import java.io.*;   4. import java.rmi.*;   5. import java.rmi.server.*;   6. import java.util.*;   7. import javax.naming.*;   8. import javax.swing.*;   9.  10. /**  11.    The client for the warehouse program.  12. */  13. public class WarehouseClient  14. {  15.    public static void main(String[] args)  16.    {  17.       try  18.       {  19.          System.setProperty("java.security.policy", "client.policy");  20.          System.setSecurityManager(new RMISecurityManager());  21.  22.          Properties props = new Properties();  23.          String fileName = "WarehouseClient.properties";  24.          FileInputStream in = new FileInputStream(fileName);  25.          props.load(in);  26.          String url = props.getProperty("warehouse.url");  27.          if (url == null)  28.             url = "rmi://localhost/central_warehouse";  29.  30.          Context namingContext = new InitialContext();  31.          Warehouse centralWarehouse = (Warehouse) namingContext.lookup(url);  32.          JFrame frame = new WarehouseClientFrame(centralWarehouse);  33.          frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  34.          frame.setVisible(true);  35.       }  36.       catch (Exception e)  37.       {  38.          e.printStackTrace();  39.       }  40.    }  41. }  42.  43. /**  44.    A frame to select the customer's age, sex, and hobbies, and to  45.    show the matching products resulting from a remote call to the  46.    warehouse.  47. */  48. class WarehouseClientFrame extends JFrame  49. {  50.    public WarehouseClientFrame(Warehouse warehouse)  51.    {  52.       this.warehouse = warehouse;  53.       setTitle("WarehouseClient");  54.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);  55.  56.       JPanel panel = new JPanel();  57.       panel.setLayout(new GridLayout(0, 2));  58.  59.       panel.add(new JLabel("Age:"));  60.       age = new JTextField(4);  61.       age.setText("20");  62.       panel.add(age);  63.  64.       female = new JRadioButton("Female", true);  65.       male = new JRadioButton("Male", true);  66.       ButtonGroup group = new ButtonGroup();  67.       panel.add(female); group.add(female);  68.       panel.add(male); group.add(male);  69.  70.       panel.add(new JLabel("Hobbies: "));  71.       hobbies = new ArrayList<JCheckBox>();  72.       for (String h : new String[] { "Gardening", "Beauty", "Computers", "Household",  "Sports" })  73.       {  74.          JCheckBox checkBox = new JCheckBox(h);  75.          hobbies.add(checkBox);  76.          panel.add(checkBox);  77.       }  78.  79.       result = new JTextArea(4, 40);  80.       result.setEditable(false);  81.  82.       JPanel buttonPanel = new JPanel();  83.       JButton submitButton = new JButton("Submit");  84.       buttonPanel.add(submitButton);  85.       submitButton.addActionListener(new  86.          ActionListener()  87.          {  88.             public void actionPerformed(ActionEvent event)  89.             {  90.                callWarehouse();  91.             }  92.          });  93.  94.       add(panel, BorderLayout.NORTH);  95.       add(result, BorderLayout.CENTER);  96.       add(buttonPanel, BorderLayout.SOUTH);  97.    }  98.  99.    /** 100.       Call the remote warehouse to find matching products. 101.    */ 102.    private void callWarehouse() 103.    { 104.       try 105.       { 106.          ArrayList<String> selected = new ArrayList<String>(); 107.          for (JCheckBox checkBox : hobbies) 108.             if (checkBox.isSelected()) selected.add(checkBox.getText()); 109.          Customer c = new Customer(Integer.parseInt(age.getText()), 110.             (male.isSelected() ? Product.MALE : 0) 111.             + (female.isSelected() ? Product.FEMALE : 0), 112.             selected.toArray(new String[selected.size()])); 113.          ArrayList<Product> recommendations = warehouse.find(c); 114.          result.setText(c + "\n"); 115.          for (Product p : recommendations) 116.          { 117.             String t = p.getDescription() + "\n"; 118.             result.append(t); 119.          } 120.       } 121.       catch (Exception e) 122.       { 123.          e.printStackTrace(); 124.          result.setText("Exception: " + e); 125.       } 126.    } 127. 128.    private static final int DEFAULT_WIDTH = 300; 129.    private static final int DEFAULT_HEIGHT = 300; 130. 131.    private Warehouse warehouse; 132.    private JTextField age; 133.    private JRadioButton male; 134.    private JRadioButton female; 135.    private ArrayList<JCheckBox> hobbies; 136.    private JTextArea result; 137. } 

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 with the same type 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 machineall 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 subclass doesn't implement a remote interface but a superclass does, and an object of the subclass is passed to a remote method, only the superclass 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; }    private 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 superclass ProductImpl because only that class implements a remote interface (see Figure 5-9). Thus, in this case, the getStockCode method isn't available remotely.

Figure 5-9. 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-10 shows the inheritance diagram.

Figure 5-10. BookImpl has additional remote methods


Now, when a book object is passed 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 in which you will use this feature. Suppose you receive a remote object through a variable of type Product.

 ArrayList<Product> result = centralWarehouse.find(c); for (Product p : result) {    . . . } 

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. Instead, 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.

Remote Objects and the equals and hashCode Methods

As you saw in Chapter 2, objects inserted in sets must override the equals method. In the case of a hash set or hash map, the hashCode method must be defined as well. 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. Because 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, the equals and hashCode methods on stub objects simply look at the location of the server objects. The equals method deems two stubs equal if they refer to the same server object. 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.

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

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. However, the clone method in the Object superclass promised never to throw any exception other than CloneNotSupportedException. That is the same limitation that you encountered in the previous section: equals and hashCode don't look up the remote object value at all but just compare stub references. However, it makes no sense for clone to make another clone of a stubif 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.



    Core JavaT 2 Volume II - Advanced Features
    Building an On Demand Computing Environment with IBM: How to Optimize Your Current Infrastructure for Today and Tomorrow (MaxFacts Guidebook series)
    ISBN: 193164411X
    EAN: 2147483647
    Year: 2003
    Pages: 156
    Authors: Jim Hoskins

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net