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