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
16package javassist.tools.rmi;
17
18import java.io.*;
19import java.net.*;
20import java.applet.Applet;
21import 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 */
74public 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