Home | History | Annotate | Download | only in rmi
      1 /*
      2  * Javassist, a Java-bytecode translator toolkit.
      3  * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
      4  *
      5  * The contents of this file are subject to the Mozilla Public License Version
      6  * 1.1 (the "License"); you may not use this file except in compliance with
      7  * the License.  Alternatively, the contents of this file may be used under
      8  * the terms of the GNU Lesser General Public License Version 2.1 or later.
      9  *
     10  * Software distributed under the License is distributed on an "AS IS" basis,
     11  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
     12  * for the specific language governing rights and limitations under the
     13  * License.
     14  */
     15 
     16 package javassist.tools.rmi;
     17 
     18 import java.io.*;
     19 import java.net.*;
     20 import java.applet.Applet;
     21 import java.lang.reflect.*;
     22 
     23 /**
     24  * The object importer enables applets to call a method on a remote
     25  * object running on the <code>Webserver</code> (the <b>main</b> class of this
     26  * package).
     27  *
     28  * <p>To access the remote
     29  * object, the applet first calls <code>lookupObject()</code> and
     30  * obtains a proxy object, which is a reference to that object.
     31  * The class name of the proxy object is identical to that of
     32  * the remote object.
     33  * The proxy object provides the same set of methods as the remote object.
     34  * If one of the methods is invoked on the proxy object,
     35  * the invocation is delegated to the remote object.
     36  * From the viewpoint of the applet, therefore, the two objects are
     37  * identical. The applet can access the object on the server
     38  * with the regular Java syntax without concern about the actual
     39  * location.
     40  *
     41  * <p>The methods remotely called by the applet must be <code>public</code>.
     42  * This is true even if the applet's class and the remote object's classs
     43  * belong to the same package.
     44  *
     45  * <p>If class X is a class of remote objects, a subclass of X must be
     46  * also a class of remote objects.  On the other hand, this restriction
     47  * is not applied to the superclass of X.  The class X does not have to
     48  * contain a constructor taking no arguments.
     49  *
     50  * <p>The parameters to a remote method is passed in the <i>call-by-value</i>
     51  * manner.  Thus all the parameter classes must implement
     52  * <code>java.io.Serializable</code>.  However, if the parameter is the
     53  * proxy object, the reference to the remote object instead of a copy of
     54  * the object is passed to the method.
     55  *
     56  * <p>Because of the limitations of the current implementation,
     57  * <ul>
     58  * <li>The parameter objects cannot contain the proxy
     59  * object as a field value.
     60  * <li>If class <code>C</code> is of the remote object, then
     61  * the applet cannot instantiate <code>C</code> locally or remotely.
     62  * </ul>
     63  *
     64  * <p>All the exceptions thrown by the remote object are converted
     65  * into <code>RemoteException</code>.  Since this exception is a subclass
     66  * of <code>RuntimeException</code>, the caller method does not need
     67  * to catch the exception.  However, good programs should catch
     68  * the <code>RuntimeException</code>.
     69  *
     70  * @see javassist.tools.rmi.AppletServer
     71  * @see javassist.tools.rmi.RemoteException
     72  * @see javassist.tools.web.Viewer
     73  */
     74 public class ObjectImporter implements java.io.Serializable {
     75     private final byte[] endofline = { 0x0d, 0x0a };
     76     private String servername, orgServername;
     77     private int port, orgPort;
     78 
     79     protected byte[] lookupCommand = "POST /lookup HTTP/1.0".getBytes();
     80     protected byte[] rmiCommand = "POST /rmi HTTP/1.0".getBytes();
     81 
     82     /**
     83      * Constructs an object importer.
     84      *
     85      * <p>Remote objects are imported from the web server that the given
     86      * applet has been loaded from.
     87      *
     88      * @param applet    the applet loaded from the <code>Webserver</code>.
     89      */
     90     public ObjectImporter(Applet applet) {
     91         URL codebase = applet.getCodeBase();
     92         orgServername = servername = codebase.getHost();
     93         orgPort = port = codebase.getPort();
     94     }
     95 
     96     /**
     97      * Constructs an object importer.
     98      *
     99      * <p>If you run a program with <code>javassist.tools.web.Viewer</code>,
    100      * you can construct an object importer as follows:
    101      *
    102      * <ul><pre>
    103      * Viewer v = (Viewer)this.getClass().getClassLoader();
    104      * ObjectImporter oi = new ObjectImporter(v.getServer(), v.getPort());
    105      * </pre></ul>
    106      *
    107      * @see javassist.tools.web.Viewer
    108      */
    109     public ObjectImporter(String servername, int port) {
    110         this.orgServername = this.servername = servername;
    111         this.orgPort = this.port = port;
    112     }
    113 
    114     /**
    115      * Finds the object exported by a server with the specified name.
    116      * If the object is not found, this method returns null.
    117      *
    118      * @param name      the name of the exported object.
    119      * @return          the proxy object or null.
    120      */
    121     public Object getObject(String name) {
    122         try {
    123             return lookupObject(name);
    124         }
    125         catch (ObjectNotFoundException e) {
    126             return null;
    127         }
    128     }
    129 
    130     /**
    131      * Sets an http proxy server.  After this method is called, the object
    132      * importer connects a server through the http proxy server.
    133      */
    134     public void setHttpProxy(String host, int port) {
    135         String proxyHeader = "POST http://" + orgServername + ":" + orgPort;
    136         String cmd = proxyHeader + "/lookup HTTP/1.0";
    137         lookupCommand = cmd.getBytes();
    138         cmd = proxyHeader + "/rmi HTTP/1.0";
    139         rmiCommand = cmd.getBytes();
    140         this.servername = host;
    141         this.port = port;
    142     }
    143 
    144     /**
    145      * Finds the object exported by the server with the specified name.
    146      * It sends a POST request to the server (via an http proxy server
    147      * if needed).
    148      *
    149      * @param name      the name of the exported object.
    150      * @return          the proxy object.
    151      */
    152     public Object lookupObject(String name) throws ObjectNotFoundException
    153     {
    154         try {
    155             Socket sock = new Socket(servername, port);
    156             OutputStream out = sock.getOutputStream();
    157             out.write(lookupCommand);
    158             out.write(endofline);
    159             out.write(endofline);
    160 
    161             ObjectOutputStream dout = new ObjectOutputStream(out);
    162             dout.writeUTF(name);
    163             dout.flush();
    164 
    165             InputStream in = new BufferedInputStream(sock.getInputStream());
    166             skipHeader(in);
    167             ObjectInputStream din = new ObjectInputStream(in);
    168             int n = din.readInt();
    169             String classname = din.readUTF();
    170             din.close();
    171             dout.close();
    172             sock.close();
    173 
    174             if (n >= 0)
    175                 return createProxy(n, classname);
    176         }
    177         catch (Exception e) {
    178             e.printStackTrace();
    179             throw new ObjectNotFoundException(name, e);
    180         }
    181 
    182         throw new ObjectNotFoundException(name);
    183     }
    184 
    185     private static final Class[] proxyConstructorParamTypes
    186         = new Class[] { ObjectImporter.class, int.class };
    187 
    188     private Object createProxy(int oid, String classname) throws Exception {
    189         Class c = Class.forName(classname);
    190         Constructor cons = c.getConstructor(proxyConstructorParamTypes);
    191         return cons.newInstance(new Object[] { this, new Integer(oid) });
    192     }
    193 
    194     /**
    195      * Calls a method on a remote object.
    196      * It sends a POST request to the server (via an http proxy server
    197      * if needed).
    198      *
    199      * <p>This method is called by only proxy objects.
    200      */
    201     public Object call(int objectid, int methodid, Object[] args)
    202         throws RemoteException
    203     {
    204         boolean result;
    205         Object rvalue;
    206         String errmsg;
    207 
    208         try {
    209             /* This method establishes a raw tcp connection for sending
    210              * a POST message.  Thus the object cannot communicate a
    211              * remote object beyond a fire wall.  To avoid this problem,
    212              * the connection should be established with a mechanism
    213              * collaborating a proxy server.  Unfortunately, java.lang.URL
    214              * does not seem to provide such a mechanism.
    215              *
    216              * You might think that using HttpURLConnection is a better
    217              * way than constructing a raw tcp connection.  Unfortunately,
    218              * URL.openConnection() does not return an HttpURLConnection
    219              * object in Netscape's JVM.  It returns a
    220              * netscape.net.URLConnection object.
    221              *
    222              * lookupObject() has the same problem.
    223              */
    224             Socket sock = new Socket(servername, port);
    225             OutputStream out = new BufferedOutputStream(
    226                                                 sock.getOutputStream());
    227             out.write(rmiCommand);
    228             out.write(endofline);
    229             out.write(endofline);
    230 
    231             ObjectOutputStream dout = new ObjectOutputStream(out);
    232             dout.writeInt(objectid);
    233             dout.writeInt(methodid);
    234             writeParameters(dout, args);
    235             dout.flush();
    236 
    237             InputStream ins = new BufferedInputStream(sock.getInputStream());
    238             skipHeader(ins);
    239             ObjectInputStream din = new ObjectInputStream(ins);
    240             result = din.readBoolean();
    241             rvalue = null;
    242             errmsg = null;
    243             if (result)
    244                 rvalue = din.readObject();
    245             else
    246                 errmsg = din.readUTF();
    247 
    248             din.close();
    249             dout.close();
    250             sock.close();
    251 
    252             if (rvalue instanceof RemoteRef) {
    253                 RemoteRef ref = (RemoteRef)rvalue;
    254                 rvalue = createProxy(ref.oid, ref.classname);
    255             }
    256         }
    257         catch (ClassNotFoundException e) {
    258             throw new RemoteException(e);
    259         }
    260         catch (IOException e) {
    261             throw new RemoteException(e);
    262         }
    263         catch (Exception e) {
    264             throw new RemoteException(e);
    265         }
    266 
    267         if (result)
    268             return rvalue;
    269         else
    270             throw new RemoteException(errmsg);
    271     }
    272 
    273     private void skipHeader(InputStream in) throws IOException {
    274         int len;
    275         do {
    276             int c;
    277             len = 0;
    278             while ((c = in.read()) >= 0 && c != 0x0d)
    279                 ++len;
    280 
    281             in.read();  /* skip 0x0a (LF) */
    282         } while (len > 0);
    283     }
    284 
    285     private void writeParameters(ObjectOutputStream dout, Object[] params)
    286         throws IOException
    287     {
    288         int n = params.length;
    289         dout.writeInt(n);
    290         for (int i = 0; i < n; ++i)
    291             if (params[i] instanceof Proxy) {
    292                 Proxy p = (Proxy)params[i];
    293                 dout.writeObject(new RemoteRef(p._getObjectId()));
    294             }
    295             else
    296                 dout.writeObject(params[i]);
    297     }
    298 }
    299