Source code: mucode/MuServer.java
1 /* mucode - A lightweight and flexible mobile code toolkit
2 * Copyright (C) 2000, Gian Pietro Picco
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 */
18 package mucode;
19
20 import java.io.*;
21 import java.net.*;
22 import java.util.*;
23 import java.lang.reflect.*;
24
25 /** Provides an abstraction of the run-time support needed to receive and/or
26 * execute mobile code.
27 *
28 * A <code>MuServer</code> object does not enable per se the handling of
29 * incoming mobile code, rather they provide only the capability to ship
30 * mobile code to other <i>µ</i>Servers. In order to accept incoming
31 * mobile code, a thread listening for incoming groups must be explicitly
32 * started in the <i>µ</i>Server by calling the {@link #boot}
33 * method. This choice minimizes the overhead introduced by the run-time
34 * support, wasting a thread only when really needed. Multiple servers can be
35 * started on the same Java VM or on different Java VMs on the same host, as
36 * long as their are given different port numbers. However, the name spaces
37 * defined by their (shared) {@link ClassSpace class spaces} are disjoint, and
38 * their sets of ubiquitous classes (see {@link #isUbiquitous} and {@link
39 * MuClassLoader}) may be different.<p>
40 *
41 * @author <a href="mailto:picco@elet.polimi.it">Gian Pietro Picco</a>
42 * @version 1.0
43 *
44 * @see ClassSpace
45 * @see Group
46 * @see GroupHandler */
47 public class MuServer extends Thread implements MuConstants {
48 static final String ERRMSG = "Impossible to retrieve the bytecode for ";
49 // The following must be declared here to avoid forward reference.
50
51 private static final String builtinUbiPackages =
52 "java.* javax.* " + PACKAGE_NAME + ".*";
53
54 /** The default properties. */
55 protected static Properties defaultProperties = new Properties();
56 static {
57 defaultProperties.put(MESSAGESkey, String.valueOf(true));
58 defaultProperties.put(ERRORSkey, String.valueOf(true));
59 defaultProperties.put(DEBUGkey, String.valueOf(false));
60 defaultProperties.put(COMPRESSIONkey, String.valueOf(false));
61
62 defaultProperties.put(PORTkey, String.valueOf(SERVER_PORT));
63 defaultProperties.put(TIMEOUTkey, String.valueOf(TIMEOUT));
64 defaultProperties.put(UBICLASSESkey, "");
65 defaultProperties.put(UBIPACKAGESkey, builtinUbiPackages);
66 }
67
68 /** The properties of this <i>µ</i>Server. Properties are stored as
69 * pairs <code><key,value></code>. The set of legal keys (here, the
70 * constantsare shown, rather than the actual key string), their
71 * corresponding default value, and their meaning is: <P>
72 *
73 * <TABLE NOSAVE >
74 * <TR>
75 * <TD><B>Key</B></TD>
76 * <TD><B>Default value</B></TD>
77 * <TD><B>Description</B></TD>
78 * </TR>
79 * <TR>
80 * <TD><code>MESSAGESkey</code></TD>
81 * <TD><code>true</code></TD>
82 * <TD>Activates server messages on the standard output stream.</TD>
83 * </TR>
84 * <TR>
85 * <TD><code>ERRORSkey</code></TD>
86 * <TD><code>true</code></TD>
87 * <TD>Activates errors messages on the standard error stream.</TD>
88 * </TR>
89 * <TR>
90 * <TD><code>DEBUGkey</code></TD>
91 * <TD><code>false</code></TD>
92 * <TD>Activates debugging messages on the standard output stream.</TD>
93 * </TR>
94 * <TR>
95 * <TD><code>COMPRESSIONkey</code></TD>
96 * <TD><code>true</code></TD>
97 * <TD>Activates compression of the serialized stream containing outgoing mobile code.</TD>
98 * </TR>
99 * <TR>
100 * <TD><code>PORTkey</code></TD>
101 * <TD><code>1968</code></TD>
102 * <TD>The port number for incoming connections.</TD>
103 * </TR>
104 * <TR>
105 * <TD><code>TIMEOUTkey</code></TD>
106 * <TD><code>30000</code></TD>
107 * <TD>The timeout for network connections.</TD>
108 * </TR>
109 * </TABLE> <P>
110 *
111 * Properties can be set using the method {@link #setProperty setProperty},
112 * and retrieved using the methods of {@link java.util.Properties}.*/
113 public Properties properties = new Properties(defaultProperties);
114
115
116 private int port = SERVER_PORT;
117 private long timeout = TIMEOUT;
118 private boolean compressionON = false;
119 private boolean errorsON = true;
120 private boolean debugON = false;
121 private boolean messagesON = true;
122
123 /** The class loader used to handle incoming groups. */
124 protected MuClassLoader incomingLoader = null;
125
126 /** The class loader for all non-mobile threads. There's only one per VM
127 * and doesn't have any server associated with it. */
128 protected static MuClassLoader defaultLoader = new MuClassLoader(null);
129 /** The class space visible outside the server. */
130
131 // It used to be like this: ClassSpace sharedSpace = new ClassSpace();
132 // However, effectively the "private" class space for the default loader
133 // includes the shared class space, as it is host-wide.
134 protected ClassSpace sharedSpace = defaultLoader.getClassSpace();
135
136 /** The "persistency root" for class loaders.
137 Key: Thread object. Value: class loader. */
138 protected static Hashtable loaders = new Hashtable(100);
139
140 /** Needed to perform the shutdown of the server. */
141 private Thread serverThread = null;
142
143 /** Holds the list of packages that are ubiquitous, and whose subpackages
144 are considered ubiquitous as well. */
145 private Vector ubiquitousPackagePrefixes = new Vector(11,1);
146 /** Holds the list of ubiquitous packages. */
147 private Vector ubiquitousPackages = new Vector(11,1);
148 /** Holds the list of ubiquitous classes that are not in ubiquitous
149 packages. */
150 private Vector ubiquitousClasses = new Vector(11,1);
151
152 /** Contains all the directories listed in the <code>CLASSPATH</code> system
153 * variable. Used for locating a class in the system. */
154 static String[] classpath;
155 /** The static initializer for codeBase. */
156 static {
157 Vector cp = new Vector();
158 StringTokenizer st =
159 new StringTokenizer(System.getProperty("java.class.path"),
160 System.getProperty("path.separator"));
161 while (st.hasMoreTokens())
162 cp.addElement(st.nextToken() + File.separatorChar);
163 classpath = new String[cp.size()];
164 cp.copyInto(classpath);
165 }
166
167 /** The <i>µ</i>Server constructor. */
168 public MuServer() {
169 ubiquitousPackagePrefixes.addElement("java.");
170 ubiquitousPackagePrefixes.addElement("javax.");
171 ubiquitousPackagePrefixes.addElement(PACKAGE_NAME + ".");
172 loadPropertyVars();
173 }
174
175 /**
176 * Set the value of a property. See {@link #properties} for a list of key,
177 * default values, and their meaning. */
178 public void setProperty(String key, String value) {
179 properties.put(key, value);
180 loadPropertyVars();
181 }
182
183 /**
184 * Loads into the server the properties read from a file. The format is the
185 * same used by Java, i.e., <code>key=value</code> on separate lines.
186 * The following would be a property file for the default values:
187 * <PRE>
188 * messages=true
189 * errors=true
190 * debug=false
191 * compression=true
192 * port=1968
193 * timeout=30000
194 * ubiclasses=
195 * ubipackages=java.* javax.* mucode.*
196 * </pre>
197 * @param filename the name of the property file.
198 */
199 public void loadProperties(String filename)
200 throws FileNotFoundException, IOException {
201 properties.load(new FileInputStream(new File(filename)));
202 properties.list(System.out);
203 loadPropertyVars();
204 }
205
206 private void loadPropertyVars() {
207 debugON =
208 Boolean.valueOf(properties.getProperty(DEBUGkey)).booleanValue();
209 messagesON =
210 Boolean.valueOf(properties.getProperty(MESSAGESkey)).booleanValue();
211 errorsON =
212 Boolean.valueOf(properties.getProperty(ERRORSkey)).booleanValue();
213 compressionON =
214 Boolean.valueOf(properties.getProperty(COMPRESSIONkey)).booleanValue();
215
216 port =
217 Integer.valueOf(properties.getProperty(PORTkey)).intValue();
218 timeout =
219 Long.valueOf(properties.getProperty(TIMEOUTkey)).intValue();
220
221 StringTokenizer st =
222 new StringTokenizer(properties.getProperty(UBICLASSESkey), " ");
223 while (st.hasMoreTokens())
224 ubiquitousClasses.addElement(st.nextToken());
225
226 st = new StringTokenizer(properties.getProperty(UBIPACKAGESkey), " ");
227
228 String packagename = null;
229 while (st.hasMoreTokens()) {
230 packagename = st.nextToken();
231 if (packagename.endsWith(".*"))
232 ubiquitousPackagePrefixes.addElement(packagename.substring(0,packagename.length() - 1));
233 else ubiquitousPackages.addElement(packagename);
234 }
235 }
236
237 /** Retrieve the socket port the <i>µ</i>Server is listening to. */
238 public int getPort() { return port; }
239 /** Retrieve the timeout (in milliseconds) for synchronous transfers. */
240 public long getTimeout() { return timeout; }
241 /** Return <code>true</code>, if GZIP compression is used during outgoing
242 communication, <code>false</code> otherwise. */
243 public boolean isCompressionOn() { return compressionON; }
244 /** Return <code>true</code>, if debug messages are shown on the standard
245 output stream, <code>false</code> otherwise. */
246 public boolean isDebugOn() { return debugON; }
247 /** Return <code>true</code>, if error messages are shown on the standard
248 error stream, <code>false</code> otherwise. */
249 public boolean isErrorsOn() { return errorsON; }
250 /** Return <code>true</code>, if messages are shown on the standard
251 output stream, <code>false</code> otherwise. */
252 public boolean isMessagesOn() { return messagesON; }
253
254
255 /** Specify the packages that are considered to be ubiquitous, i.e., whose
256 * classes are assumed to be present on every node. Ubiquitous classes are
257 * the first one to be searched by the class loader. If
258 * <code>packageName</code> ends with <code>.*</code>, all the subpackages
259 * are made ubiquitous as well. Thus, for instance, calling this method by
260 * passing <code>("mypackage")</code> results in considering
261 * ubiquitous only all the classes in <code>mypackage</code>, but not for
262 * instance those in <code>mypackage.mysubpackage</code>. By default, system
263 * classes (<code>java.*</code> and subpackages), Java extensions
264 * (<code>javax.*</code> and subpackages), and <i>µ</i>Code classes
265 * (<code>mucode.*</code> and subpackages) are ubiquitous.
266 *
267 * @param package The package name to be added. It is specified using the
268 * standard Java convention for naming packages, using the dot notation.
269 * @param subpackages If <code>true</code>, all subpackages of
270 * <code>packageName</code> are set to ubiquitous as well. */
271 public final void addUbiquitousPackage(String packageName) {
272 setProperty(UBIPACKAGESkey,
273 properties.getProperty(UBIPACKAGESkey) + " " + packageName);
274 insertUbiquitousPackage(packageName);
275 }
276
277 private final void insertUbiquitousPackage(String packageName) {
278 if (packageName.endsWith(".*"))
279 ubiquitousPackagePrefixes.addElement(packageName.substring(0,packageName.length() - 1));
280 else ubiquitousPackages.addElement(packageName + ".");
281 }
282
283 /** Add all the classes contained in a package to the set of ubiquitous
284 * classes, i.e., the classes present on every node. Ubiquitous classes are
285 * the first one to be searched by the class loader. System classes
286 * (<code>java.*</code>, and the primitive types) and µCode classes
287 * (<code>mucode.*</code>) are ubiquitous by default.
288 *
289 * @param className The (fully specified) name of the class to be added. It
290 * is specified using the standard Java convention for naming packages,
291 * using the dot notation. */
292 public final void addUbiquitousClass(String className) {
293 setProperty(UBICLASSESkey,
294 properties.getProperty(UBICLASSESkey) + " " + className);
295 ubiquitousClasses.addElement(className);
296 }
297
298 /** Return <code>true</code> if a class is ubiquitous, (i.e., it is assumed
299 * to be present on every node), <code>false</code> otherwise.
300 *
301 * @param className The (fully specified) name of the class to be
302 * searched. It is specified using the standard Java convention for naming
303 * packages, using the dot notation (e.g., <code>java.lang.Thread</code>. */
304 public final boolean isUbiquitous(String className) {
305 boolean inUPackagePrefixes = false;
306 boolean inUPackages = false;
307 boolean inUClasses = false;
308 Enumeration e = null;
309 String root = null;
310
311 e = ubiquitousPackagePrefixes.elements();
312 while (!inUPackagePrefixes && e.hasMoreElements()) {
313 root = (String) e.nextElement();
314 inUPackagePrefixes = className.startsWith(root);
315 }
316 if (!inUPackagePrefixes) {
317 e = ubiquitousPackages.elements();
318 while (!inUPackages && e.hasMoreElements()) {
319 root = (String) e.nextElement();
320 inUPackages = (className.startsWith(root) &&
321 className.indexOf(".", root.length()) == -1);
322 }
323 }
324 if (!inUPackagePrefixes && !inUPackages) {
325 e = ubiquitousClasses.elements();
326 while (!inUClasses && e.hasMoreElements()) {
327 root = (String) e.nextElement();
328 inUClasses = className.equals(root);
329 }
330 }
331 return (inUPackagePrefixes || inUPackages || inUClasses);
332 }
333
334 /** Create a new <code>Group</code> object. This operation must be requested
335 * to a specific <i>µ</i>Server rather than being performed by
336 * application code, because resolution of the group classes depend on the
337 * contents of the class spaces associated to a particular
338 * <i>µ</i>Server.
339 *
340 * @param rootClass the root class for the group.
341 * @param handlerClass the handler for the group. It cannot be
342 * <code>null</code>.
343 * @return the group created by the <i>µ</i>Server. */
344 public final Group createGroup(String root, String handler) {
345 if (handler == null)
346 throw new IllegalArgumentException("The handler " +
347 "class of a group cannot be null");
348 return new Group(root, handler, this);
349 }
350
351 /** Retrieve the shared class space associated with the <i>µ</i>Server
352 * containing the thread object invoking the method.
353 */
354 public ClassSpace getSharedClassSpace() {
355 return sharedSpace;
356 }
357
358 /** Retrieves the private class space associated with the thread object
359 * invoking the method.
360 */
361 public final ClassSpace getPrivateClassSpace() {
362 return ClassSpace.getCurrent();
363 }
364
365 /** Start the run-time support listening for incoming groups. */
366 public final void boot() {
367 setName("MuServer");
368 start();
369 }
370
371 /** Stop the run-time support listening for incoming groups. */
372 public final void shutDown() {
373 stop();
374 }
375
376 /** Retrieve the <i>µ</i>Server managing the thread object invoking
377 * this method. If the thread object is a stationary one, i.e. it has not
378 * been created as a consequence of a migration, the method returns
379 * <code>null</code>.
380 *
381 * @return the <i>µ</i>Server managing the invoking thread. */
382 public final static MuServer getServer() {
383 MuServer r = null;
384 MuClassLoader l = MuClassLoader.getCurrent();
385
386 if (l != null) r = l.getServer();
387 return r;
388 }
389
390
391 /** Retrieve the <i>µ</i>Server managing the thread object passed as a
392 * parameter. If the thread object is a stationary one, i.e. it has not
393 * been created as a consequence of a migration, the method returns
394 * <code>null</code>.
395 * @return the <i>µ</i>Server managing the thread object passed as a parameter. */
396 public final static MuServer getServer(Thread t) {
397 MuServer r = null;
398 MuClassLoader l = (MuClassLoader) loaders.get(t);
399
400 if (l != null) r = l.getServer();
401 return r;
402 }
403
404
405 /** Retrieve the <i>µ</i>Server associated with the given object. If
406 * the object has not been inside a <i>µ</i>Server, the method returns
407 * <code>null</code>.
408 *
409 * @return the <i>µ</i>Server associated to the given object. */
410 public final static MuServer getServer(Object obj) {
411 MuServer ret = null;
412 ClassLoader classLoader = obj.getClass().getClassLoader();
413
414 if (classLoader instanceof MuClassLoader)
415 ret = ((MuClassLoader) classLoader).getServer();
416 return ret;
417 }
418
419 /** Implements the <code>Runnable</code> interface, and contains the
420 * behavior of the server. */
421 public void run() {
422 Socket clientSocket = null;
423 ServerSocket servSocket = null;
424 Group group = null;
425 boolean problem = false;
426 GroupHandler handler = null;
427 Thread t = null;
428 Header header = null;
429
430 long start = 0;
431
432 // Accept connections.
433 try {
434 servSocket = new ServerSocket(port);
435 } catch (IOException e) {
436 Err("Cannot listen on port " + port + ". Halting.");
437 System.exit(0);
438 }
439 M("MuServer activated on port "+port);
440 // Continuously process requests.
441 for(;;) {
442 try {
443 clientSocket = servSocket.accept();
444 D("Connection accepted. Reconstructing the header...");
445 start = System.currentTimeMillis();
446 // The server is passed to enable messages.
447 header = new Header(clientSocket);
448 } catch(IOException e) {
449 Err("Accept failed on port " + port, e);
450 }
451 // Reads the header to determine whether is a request for dynamic linking
452 // or a normal operation.
453 switch (header.dataType) {
454 case DYN_LINK:
455 D("The connection contains a request for dynamic linking of class: " +
456 header.className);
457 DataOutputStream os = null;
458 try {
459 os = new DataOutputStream(clientSocket.getOutputStream());
460 D("Contents of shared class space: "
461 + getSharedClassSpace().toString());
462 byte[] requestedBC =
463 getSharedClassSpace().getClassByteCode(header.className);
464 if (requestedBC == null) {
465 D("Bytecode for " + header.className + " doesn't exist");
466 os.write(REMOTE_ERROR);
467 } else {
468 os.writeInt(requestedBC.length);
469 os.write(requestedBC);
470 D("Bytecode for " + header.className +
471 " (" + requestedBC.length+ " bytes) sent.");
472 }
473 } catch (IOException e) {
474 Err("I/O errors during remote dynamic linking.", e);
475 } finally {
476 try {
477 os.flush();
478 os.close();
479 } catch (IOException e) {
480 Err("I/O errors during remote dynamic linking.", e);
481 }
482 }
483 break;
484 case GROUP:
485 try {
486 D("The connection contains a group. Reconstructing ... " +
487 header.className);
488 group = new Group(clientSocket, header.compressedStream, this);
489 // If we're not requested to send back error messages, we can close
490 // the connection.
491 if (!group.isSynchronousTransfer()) clientSocket.close();
492 } catch (IOException e) {
493 Err("I/O errors during group deserialization.", e);
494 problem = true;
495 } catch (ClassNotFoundException e) {
496 Err("Problems during class loading.", e);
497 problem = true;
498 } catch (DuplicateClassException e) {
499 Err("The group is not allowed to overwrite a class "+
500 "in the class space.", e);
501 problem = true;
502 }
503 // If some problem occurs, we print the error on the console and simply
504 // skip the request processing.
505 if (!problem) {
506 // Can we allow a handler to be loaded dynamically? Why not...
507 // But if it has many classes, should be a separate namespace? Boh.
508 // @@@ Qui c'e' bisogno di mettere dell'altra roba.
509 try {
510 handler = (GroupHandler) group.getHandlerClass().newInstance();
511 D("Handler created.");
512 } catch (IllegalAccessException e) {
513 Err("The handler class must public!.", e);
514 } catch (InstantiationException e) {
515 Err("The handler class cannot be an interface " +
516 "or an abstract class.", e);
517 }
518 try {
519 D("Unpacking the group.");
520 t = handler.unpack(group);
521 D("Group unpacked.");
522 } catch (MuCodeException e) {
523 Err("Problems during the unpack() of a group: " + e, e);
524 }
525 if (t != null) {
526 if(!loaders.containsKey(t))
527 loaders.put(t, incomingLoader);
528 t.start();
529 D("Time to activate the group: " +
530 (System.currentTimeMillis() - start));
531 D("Group thread ("+ t.getName()+ "spawned.");
532 }
533 }
534 break;
535 default: Err("Illegal data in group header:" + header.dataType);
536 }
537 incomingLoader = null;
538 cleanup();
539 Thread.yield();
540 }
541 }
542
543 protected void cleanup() {
544 Enumeration keys = loaders.keys();
545 while (keys.hasMoreElements()) {
546 Thread keyObj = (Thread) keys.nextElement();
547 if (!keyObj.isAlive()) {
548 loaders.remove(keyObj);
549 D("Garbage collected a class loader: " + loaders.size() + " left.");
550 }
551 }
552 }
553
554 /******* Output Message handling ********/
555
556 void M(String msg) {
557 if (messagesON) System.out.println(PACKAGE_NAME + ": " + msg);
558 }
559 void D(String msg) { if (debugON) M(msg); }
560 void Err(String msg) {
561 if (errorsON) System.err.println(PACKAGE_NAME + ": " + msg); }
562
563 void D(String msg, Throwable e) {
564 D(msg);
565 e.printStackTrace();
566 }
567
568 void Err(String msg, Throwable e) {
569 Err(msg);
570 if (debugON) e.printStackTrace();
571 }
572
573 /******* NetUtil ********/
574
575 /** Extracts the host component from an address specified as
576 * <code>host:port</code>.
577 *
578 * @param destination The address to be parsed.
579 * @return The host address.
580 *
581 * @exception java.net.UnknownHostException if the result correspond to an
582 * invalid host.*/
583 static InetAddress parseHost(String destination)
584 throws UnknownHostException {
585 StringTokenizer st = new StringTokenizer(destination, ":");
586 String dest = null;
587
588 if (st.countTokens() == 1) dest = destination;
589 else dest = (String) st.nextElement();
590 return InetAddress.getByName(dest);
591 }
592
593 /** Extracts the port component from an address specified as
594 * <code>host:port</code>. If the port is not specified, the default value
595 * as specified in the class <code>Properties</code> is returned.
596 *
597 * @param destination The address to be parsed.
598 * @return The port number.
599 *
600 * @exception java.net.UnknownHostException if the result correspond to an
601 * invalid host.*/
602 static int parsePort(String destination) throws UnknownHostException {
603 StringTokenizer st = new StringTokenizer(destination, ":");
604 int port = -1;
605
606 if (st.countTokens() > 1) {
607 String dummy = (String) st.nextElement();
608 port = Integer.valueOf((String) st.nextElement()).intValue();
609 }
610 return port;
611 }
612
613 protected MuClassLoader createClassLoader() {
614 incomingLoader = new MuClassLoader(this);
615 return incomingLoader;
616 }
617 }
618
619
620
621 // try {
622 // if (group.synch) {
623 // new ObjectOutputStream(clientSocket.getOutputStream()).writeInt(OK);
624 // clientSocket.close();
625 // }
626 // } catch (IOException e) {
627 // Err("Problems with socket communication");
628 // e.printStackTrace();
629 // }
630 // group = null;
631
632 // e.printStackTrace();
633 // try {
634 // if (group.synch) new ObjectOutputStream(clientSocket.getOutputStream()).writeInt(REMOTE_ERROR);
635 // } catch (IOException e2) {
636 // Err("Problems with socket communication");
637 // e.printStackTrace();
638 // }
639 // } catch (IllegalAccessException e) {
640 // Err("Problems in restarting the thread while executing rSpawnClassClosure()");
641 // e.printStackTrace();
642 // try {
643 // if (group.synch) new ObjectOutputStream(clientSocket.getOutputStream()).writeInt(REMOTE_ERROR);
644 // } catch (IOException e2) {
645 // Err("Problems with socket communication");
646 // e.printStackTrace();
647 // }
648 // } catch (InstantiationException e) {
649 // Err("Problems in instantiating the thread while executing rSpawnClassClosure()");
650 // e.printStackTrace();
651 // try {
652 // if (group.synch) new ObjectOutputStream(clientSocket.getOutputStream()).writeInt(REMOTE_ERROR);
653 // } catch (IOException e2) {
654 // Err("Problems with socket communication");
655 // e.printStackTrace();
656 // }