Return-Path: <Mailing@hermes.java.sun.com>
Received: from fort-point-station.mit.edu by po10.mit.edu (8.9.2/4.7) id SAA14265; Tue, 27 Mar 2001 18:54:36 -0500 (EST)
Received: from hermes.java.sun.com (hermes.java.sun.com [204.160.241.85])
	by fort-point-station.mit.edu (8.9.2/8.9.2) with SMTP id SAA08767
	for <alexp@mit.edu>; Tue, 27 Mar 2001 18:55:09 -0500 (EST)
Date: Tue, 27 Mar 2001 18:55:09 -0500 (EST)
Message-Id: <200103272355.SAA08767@fort-point-station.mit.edu>
From: "JDC Tech Tips"<Mailing@hermes.java.sun.com>
To: alexp@mit.edu
Subject: JDC Tech Tips  March 27, 2001
Reply-To: JDCTechTips@hermes.java.sun.com
Errors-To: bounced_mail@hermes.java.sun.com
Precedence: junk
Mime-Version: 1.0
Content-Type: text/plain; charset=us-ascii
X-Mailer: Beyond Email 2.2


 J  D  C    T  E  C  H    T  I  P  S

                      TIPS, TECHNIQUES, AND SAMPLE CODE


WELCOME to the Java Developer Connection(sm) (JDC) Tech Tips, 
March 27, 2001. This issue of the JDC Tech Tips covers 
the following topics:
 
         * Deserializing Marshalled Objects
         * JNDI Lookup in Distributed Systems

This tip was developed using Java(tm) 2 SDK, Standard Edition, 
v 1.3.

This issue of the JDC Tech Tips is written by Stuart Halloway,
a Java specialist at DevelopMentor (http://www.develop.com/java).

You can view this issue of the Tech Tips on the Web at
http://java.sun.com/jdc/JDCTechTips/2001/tt0327.html

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
DESERIALIZING MARSHALLED OBJECTS

When you deserialize a Java(tm) object from a stream, all 
reference objects are deserialized as well. An important 
exception to this is the marshalled object, that is, an 
instance of the class java.rmi.MarshalledObject. The
constructor of this class takes as a parameter an object to be 
serialized. The constructor creates a marshalled object that 
contains the serialized representation of the supplied object. 
In other words, a marshalled object is a container for the 
serialized representation of another object. 

A marshalled object has a special property: when you deserialize
a marshalled object, the contained object is not automatically 
deserialized. Instead, the contained object must be deserialized 
in an explicit step. This explicit step means more work for you, 
but it provides two major advantages, as follows:

o With marshalled objects, you can postpone deserialization
  until you actually need the object. If an object must pass
  through many virtual machines before it reaches its
  destination, normal serialization requires that you
  deserialize the object at each step. This, in turn, requires
  that the necessary classes be available and loaded on each
  intermediate machine, which is not always possible or
  desirable.

o Marshalled objects can contain a codebase URL where the class
  code can be found. When you use a marshalled object, any
  needed class file is automatically located.

To see these advantages in practice, let's compare a marshalled
object with a "plain" serialized object. First, here's the plain 
variety:

//file PlainContainer.java
import java.io.*;
public class PlainContainer implements Serializable {
  public PlainContainer(Object containee) { 
    this.containee = containee;
  }
  private Object containee;
  public String toString() {
    return "This container holds " + containee;
  }
}

//file WritePlain.java
import java.io.*;
public class WritePlain {
  public static void main(String[] args) throws IOException {
    ObjectOutputStream oos = null;
    try {
     oos = new ObjectOutputStream(new BufferedOutputStream(
                        new FileOutputStream(args[0])));
     oos.writeObject(new PlainContainer("a string"));
    }
    finally {
      if (oos != null) oos.close();
    }
    System.out.println("Wrote to " + args[0]);
  }
}

//file ReadPlain.java
import java.io.*;
public class ReadPlain {
  public static void main(String[] args) throws Exception {
    ObjectInputStream ois = null;
    try {
      ois = new ObjectInputStream(new BufferedInputStream(
                                  new FileInputStream(args[0])));
      Object o = ois.readObject();
      System.out.println("Read from file " + args[0]+ ":");
      System.out.println(o);
    }
    catch (Exception e) {
      e.printStackTrace();
    }
    finally {
      if (ois != null) ois.close();
    }
  }
}

The PlainContainer class is a simple serializable class. The
WritePlain and ReadPlain classes test reading and writing an
instance of PlainContainer. Assuming that you compile all three 
classes to a subfolder named "classes", you should be able to 
first write and then read a PlainContainer, as follows:

  >java -cp classes WritePlain plain.ser
  Wrote to plain.ser
  >java -cp classes ReadPlain plain.ser
  Read from file plain.ser:
  This container holds a string

This is fine if the PlainContainer class is available when the 
instance is deserialized. Let's see what happens if the class 
isn't available. Copy the ReadPlain class to a separate 
subfolder named "otherdir". Then try to read from the plain.ser 
file again, using otherdir as the classpath. You should see 
the following:

  >java -cp otherdir ReadPlain plain.ser
  java.lang.ClassNotFoundException: PlainContainer
        at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
        (etc...)

The default serialization mechanism preserves the state of the
instance, but not how to locate the class, so deserialization
fails. You can use a marshalled object instead to solve this
problem:

//file WriteMarshalled.java
import java.io.*;
import java.rmi.*;

public class WriteMarshalled {
  public static void main(String[] args) throws IOException {
    ObjectOutputStream oos = null;
    try {
      oos = new ObjectOutputStream(new BufferedOutputStream(
                              new FileOutputStream(args[0])));
      oos.writeObject(new MarshalledObject(new PlainContainer(
                                     "a marshalled string")));
    }
    finally {
      if (oos != null) oos.close();
    }
    System.out.println("Wrote to " + args[0]);
  }
}

//file ReadMarshalled.java
import java.io.*;
import java.rmi.*;

public class ReadMarshalled {
  public static void main(String[] args) throws Exception {
    ObjectInputStream ois = null;
    try {
      ois = new ObjectInputStream(new BufferedInputStream(
                                  new FileInputStream(args[0])));
      MarshalledObject o = (MarshalledObject) ois.readObject();
      System.out.println("Read MarshalledObject from file " +
                        args[0]+ ", contents are:");
      Object containee = o.get();
      System.out.println(containee);
      System.out.println("Marshalled object loaded by: " + 
                         o.getClass().getClassLoader());
      System.out.println("Containee loaded by: " + 
                 containee.getClass().getClassLoader());
    }
    catch (Exception e) {
      e.printStackTrace();
    }
    finally {
      if (ois != null) ois.close();
    }
  }
}

The WriteMarshalled and ReadMarshalled classes are almost 
identical to the original versions, but have a few subtle 
differences:

o WriteMarshalled wraps a PlainContainer object in a marshalled
  object, that is, and instance of MarshalledObject.

o ReadMarshalled casts the read object to MarshalledObject, and
  then makes an explicit call to get() to retrieve the 
  contained object. ReadMarshalled also prints various lines to
  to show what is happening.

Compile the new classes to the classes folder, and copy the
ReadMarshalled class to the otherdir folder. Now you are almost 
ready to see the marshalled object use dynamic class loading to 
find the classes that it needs.  However, to use dynamic class 
loading you must also set the codebase in the process that first 
serializes the object, and configure security in any process that 
wants to dynamically load classes. See the JDC Tech Tip for 
February 7, 2001, "Dynamic Class Loading in RMI" 
(http://java.sun.com/jdc/JDCTechTips/2001/tt0227.html#dynamic)
for details on how to set the codebase and how to configure 
security for dynamic class loading. Here are the necessary 
command lines:

  >java -Djava.rmi.server.codebase=file:/yourcode/classes/ \
        -cp classes WriteMarshalled marshalled.ser
  Wrote to marshalled.ser
  >java -Djava.security.policy=SimpleRMI.policy \
        -Djava.security.manager -cp otherdir ReadMarshalled \
         marshalled.ser
  Read MarshalledObject from file marshalled.ser, contents are:
  This container holds a marshalled string
  Marshalled object loaded by: null
  Containee loaded by: sun.rmi.server.LoaderHandler$Loader@30c221
  
If you wonder why the output indicates that the marshalled object 
is loaded by "null," it's because the object is loaded by the 
bootstrap class loader, which does not have an identity in the
Java environment, and so appears as "null" in the output.
  
To use these command lines, you need to replace the string
"yourcode" with your actual working directory. You also need to 
create a SimpleRMI.policy file in your working directory. Use the 
template below, replacing "yourcode" with the path to your 
working directory.

  grant {
    permission java.net.SocketPermission 
                                    "*:1024-", "accept, connect";
    permission java.io.FilePermission 
                                     "${/}yourcode${/}-", "read";
  };

As you can see from the output above, the Containee is found, 
even though it was not on the class path. The Containee is loaded 
by a dynamic class loader (sun.rmi.server.LoaderHandler$Loader).  

This might seem like a lot of work for a small benefit, 
especially because the security properties and policy file syntax 
are tricky when you first encounter them. However, the initial 
effort you make to set the codebase and process security will 
benefit all the objects in your process. Moreover, these process 
configuration steps are essential if you are building distributed 
systems. Using marshalled objects is the preferred approach if 
your objects might be deserialized in environments where the class 
files are not on the class path. It's also the preferred approach
if your objects might pass through many intermediate hosts before 
they need to be fully deserialized.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
JNDI LOOKUP IN DISTRIBUTED SYSTEMS

The Java Naming and Directory Interface(tm) (JNDI) is the Java 
platform solution for looking up objects by name or by 
attributes. You should use JNDI to access resources such as 
database connections, transactions, remote objects, and 
Enterprise JavaBeans(tm) components. Many of the technologies 
associated with these resources were designed before the JNDI 
specification was completed, and because of that, have their own 
name lookup schemes. You should always consider JNDI a better 
approach than these resource-specific naming schemes.

To illustrate the superiority of JNDI, consider the following
RMI application. 

//file Echo.java
import java.rmi.*;
public interface Echo extends Remote {
  public String echo(String value) throws RemoteException;
}

//file EchoClient.java
import java.rmi.*;
public class EchoClient {
  public static void main(String [] args) {
    try {
      System.out.println("Connecting to echo server...");
      Echo e = (Echo) Naming.lookup("ECHO");
      String result = e.echo("Hello");
      System.out.println("Echo returned " + result);
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}

//file EchoServer.java
import java.io.*;
import java.rmi.*;
import java.rmi.registry.*;
import java.rmi.server.*;
public class EchoServer extends RemoteObject implements Echo {
  public String echo(String value) {
    return value;
  }
  public static void main(String[] args) {
    try {
      EchoServer serv = new EchoServer();
      Echo remoteObj = (Echo) UnicastRemoteObject.exportObject(
                                                           serv);
      Registry r = LocateRegistry.getRegistry("localhost", 1099);
      r.rebind("ECHO", remoteObj);
      BufferedReader rdr = new BufferedReader(
                               new InputStreamReader(System.in));
      while (true) {
        System.out.println("Type EXIT to shutdown the server.");
        if ("EXIT".equals(rdr.readLine())) {
          break;
        }
      }
      r.unbind("ECHO");
      UnicastRemoteObject.unexportObject(serv, true);
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}

If this application looks familiar, it's because it was used to
illustrate the February 7, 2001 JDC Tech Tip "The Lifecycle of an 
RMI Server" 
(http://java.sun.com/jdc/JDCTechTips/2001/tt0227.html#lifecycle).
As described in that Tech Tip, the server binds a name into the 
rmiregistry through the Registry class, and the client finds this 
object with Naming.lookup. 

If you rewrite this application to use JNDI lookup, you only need
to make a few changes in the code. You need to import the core 
JNDI package, javax.naming, in the client and server. You also 
need to change the lookup code in the client to the following:

   Context ctxt = new InitialContext();
   Echo e = (Echo) ctxt.lookup("ECHO");

In the server, you use the following JNDI code to bind and unbind 
into the naming service:

   Context ctxt = new InitialContext();
   ctxt.rebind("ECHO", remoteObj);
   //object available in naming service...
   ctxt.unbind("ECHO");
   
Here are the new versions of the client and server, rewritten for
JNDI:

//new version of EchoClient.java
import java.rmi.*;
import javax.naming.*;
public class EchoClient {
  public static void main(String [] args) {
    try {
      System.out.println("Connecting to echo server...");
      Context ctxt = new InitialContext();
      Echo e = (Echo) ctxt.lookup("ECHO");
      String result = e.echo("Hello");
      System.out.println("Echo returned " + result);
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}

//new version of EchoServer.java
import java.io.*;
import java.rmi.*;
import java.rmi.registry.*;
import java.rmi.server.*;
import javax.naming.*;
public class EchoServer extends RemoteObject implements Echo {
  public String echo(String value) {
    return value;
  }
  public static void main(String[] args) {
    try {
      EchoServer serv = new EchoServer();
      Echo remoteObj = (Echo) UnicastRemoteObject.exportObject(
                                                          serv);
      Context ctxt = new InitialContext();
      ctxt.rebind("ECHO", remoteObj);
      BufferedReader rdr = new BufferedReader(
                              new InputStreamReader(System.in));
      while (true) {
        System.out.println("Type EXIT to shutdown the server.");
        if ("EXIT".equals(rdr.readLine())) {
          break;
        }
      }
      ctxt.unbind("ECHO");
      UnicastRemoteObject.unexportObject(serv, true);
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}

The difference in syntax is small, but the implications are 
large. While the rmiregistry APIs commit you at compile time to 
a specific naming service on a specific machine, the JNDI APIs 
are generic, and allow you (or an administrator) to specify a 
specific naming service at runtime. In this example, you can 
tell the InitialContext to use the rmiregistry service by 
creating a jndi.properties file with the following entries:

  java.naming.factory.initial=
                 com.sun.jndi.rmi.registry.RegistryContextFactory
  java.naming.provider.url=rmi://localhost

When the initial context is created, JNDI reads the properties
file and uses the value of java.naming.factory.initial to load a
specific provider. It then uses java.naming.provider.url to 
select the top-level naming context to search. (There are several 
other ways to specify this configuration information. See the 
"JNDI API Tutorial and Reference" 
(http://java.sun.com/docs/books/jndi/) for details.)

To test the application, you need to create a stub class:

  rmic EchoServer
  
Then open three console windows to start the RMI registry, run the 
RMI server, and run the RMI client, respectively. 

Start the RMI registry in one console window:

  rmiregistry
   
Run the RMI server in the second console window: 

  java EchoServer  
  
Run the RMI client in the third console window:

  java EchoClient

You have now come full circle. At this point, you are still using
the same naming service you were using before, so there is no
immediate benefit to this approach. However, you might choose to 
bind your RMI objects into some other naming service, such as 
CORBA or LDAP, or some other service not even invented yet. Note
however that switching to CORBA involves several subtleties in 
addition to the choice of a naming service. For more about these
subtleties, see "Java(tm) RMI-IIOP Documentation"
(http://java.sun.com/j2se/1.3/docs/guide/rmi-iiop/index.html).
But aside from the CORBA subtleties, you only need to edit the
jndi.properties file, and continue to use your existing code in
order to bind your RMI objects into another naming service. No
code changes or recompilation are needed.

For further information about JNDI, see the book "JNDI API 
Tutorial and Reference" by Rosanna Lee and Scott Seligman
(http://java.sun.com/docs/books/jndi/). Also see the book
"Mastering RMI: Developing Enterprise Applications in Java and 
EJB" by Rickard Oberg.
.  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .

- NOTE

Sun respects your online time and privacy. The Java Developer 
Connection mailing lists are used for internal Sun Microsystems 
purposes only. You have received this email because you elected 
to subscribe. To unsubscribe, go to the Subscriptions page 
(http://developer.java.sun.com/subscription/), uncheck the 
appropriate checkbox, and click the Update button.


- SUBSCRIBE

To subscribe to a JDC newsletter mailing list, go to the 
Subscriptions page (http://developer.java.sun.com/subscription/), 
choose the newsletters you want to subscribe to, and click Update.


- FEEDBACK
Comments? Send your feedback on the JDC Tech Tips to:

jdc-webmaster@sun.com


- ARCHIVES
You'll find the JDC Tech Tips archives at:

http://java.sun.com/jdc/TechTips/index.html


- COPYRIGHT
Copyright 2001 Sun Microsystems, Inc. All rights reserved.
901 San Antonio Road, Palo Alto, California 94303 USA.

This document is protected by copyright. For more information, see:

http://java.sun.com/jdc/copyright.html


- LINKS TO NON-SUN SITES
The JDC Tech Tips may provide, or third parties may provide, 
links to other Internet sites or resources. Because Sun has no 
control over such sites and resources, You acknowledge and agree 
that Sun is not responsible for the availability of such external 
sites or resources, and does not endorse and is not responsible 
or liable for any Content, advertising, products, or other 
materials on or available from such sites or resources. Sun will 
not be responsible or liable, directly or indirectly, for any 
damage or loss caused or alleged to be caused by or in connection 
with use of or reliance on any such Content, goods or services 
available on or through any such site or resource.


JDC Tech Tips 
March 27, 2001

Sun, Sun Microsystems, Java, Java Developer Connection, Java 
Remote Method Invocation, Java Naming and Directory Interface,
and Enterprise JavaBeans are trademarks or registered trademarks 
of Sun Microsystems, Inc. in the United States and other countries.

