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 */ ObjectImporter(Applet applet)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 */ ObjectImporter(String servername, int port)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 */ getObject(String name)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 */ setHttpProxy(String host, int port)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 */ lookupObject(String name)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 createProxy(int oid, String classname)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 */ call(int objectid, int methodid, Object[] args)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 skipHeader(InputStream in)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 writeParameters(ObjectOutputStream dout, Object[] params)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