001: // ServerHandlerManager.java
002: // $Id: ServerHandlerManager.java,v 1.24 2002/01/22 16:26:36 ylafon Exp $
003: // (c) COPYRIGHT MIT and INRIA, 1996.
004: // Please first read the full copyright statement in file COPYRIGHT.html
005:
006: package org.w3c.jigsaw.daemon;
007:
008: import java.util.Enumeration;
009: import java.util.Hashtable;
010: import java.util.Properties;
011: import java.util.StringTokenizer;
012:
013: import java.io.File;
014: import java.io.FileInputStream;
015: import java.io.FileNotFoundException;
016: import java.io.IOException;
017: import java.io.InputStream;
018: import java.io.PrintStream;
019:
020: import org.w3c.util.ObservableProperties;
021: import org.w3c.util.Unix;
022: import org.w3c.util.UnixException;
023:
024: import org.w3c.jigsaw.http.httpd;
025: import java.lang.reflect.Method;
026: import java.lang.reflect.InvocationTargetException;
027:
028: /**
029: * A ServerHandlerManager instance manages a set of ServerHandler.
030: */
031:
032: public class ServerHandlerManager {
033: private static final boolean debug = false;
034:
035: /**
036: * The property containing the servers to be launched at startup time.
037: * This property is a <code>|</code> separated list of server identifiers.
038: * Declaring a server to this list requires that either:
039: * <ul>
040: * <li>An appropriate
041: * <code>org.w3c.jigsaw.daemon.</code><em>identifier</em>.<code>class
042: *</code>
043: * is declared and specify the class of the server to be launched (this
044: * class should implement the ServerHandler interface.).
045: * <li>An appropriate
046: * <code>org.w3c.jigsaw.daemon.</code><em>identifier</em>.<code>clones
047: *</code>
048: * is declared and its value specify an existing server to be cloned in
049: * order to create the new server.
050: * </ul>
051: */
052: protected static final String HANDLERS_P = "org.w3c.jigsaw.daemon.handlers";
053: /**
054: * The server handler property <code>class</code> suffix.
055: */
056: public final static String CLASS_P = "org.w3c.jigsaw.daemon.class";
057: /**
058: * The server handler property <code>clones</code> prefix.
059: */
060: public final static String CLONES_P = "org.w3c.jigsaw.daemon.clones";
061: public final static String SERVER_USER_P = "org.w3c.jigsaw.daemon.user";
062: public final static String SERVER_GROUP_P = "org.w3c.jigsaw.daemon.group";
063: /**
064: * The Application-Wide server manager.
065: */
066: protected static ServerHandlerManager manager = null;
067: /**
068: * The list of running server handlers.
069: */
070: protected Hashtable handlers = null;
071: /**
072: * The server handler manager property list.
073: */
074: protected DaemonProperties props = null;
075: /**
076: * Command line options that were provided at launch time.
077: */
078: protected String commandLine[] = null;
079:
080: /**
081: * Emit a non-fatal error.
082: * @param msg The message to emit.
083: */
084:
085: protected void error(String msg) {
086: System.err.println(msg);
087: }
088:
089: /**
090: * Emit a fatal error.
091: * This will abort the whole process !
092: * @param msg The fata error message.
093: */
094:
095: protected void fatal(String msg) {
096: System.out.println("*** FATAL Error:");
097: System.out.println(msg);
098: System.exit(1);
099: }
100:
101: /**
102: * Get the command line options that were provided at launch time.
103: * @return A String array instance.
104: */
105:
106: public String[] getCommandLine() {
107: if (commandLine == null)
108: commandLine = new String[0];
109: return commandLine;
110: }
111:
112: /**
113: * For subclasses only. Used to update properties at runtime.
114: * This method is called by
115: * launchServerHandler(String id, DaemonProperties props).
116: * @param p the ServerHandlerManager properties.
117: * @see launchServerHandler
118: */
119: protected void fixProperties(Properties p) {
120: //subclassed
121: }
122:
123: /**
124: * Launch a new server handler.
125: * This method tries to launch a new server handler. If launching
126: * succeeds, it returns happily, otherwise, it emits an error message
127: * to the standard error stream.
128: * @param identifier The identifier of the server handler to be launched.
129: * @param props The properties from which the server handler should
130: * initialize itself.
131: */
132:
133: protected void launchServerHandler(String id, DaemonProperties props) {
134: ServerHandler server = null;
135: String cls = props.getString(id + "." + CLASS_P, null);
136: if (cls != null) {
137: // This is a fresh new server handler:
138: try {
139: Class c = Class.forName(cls);
140: server = (ServerHandler) c.newInstance();
141: ObservableProperties p = props.loadPropertySpace(id);
142: this .fixProperties(p);
143: server.initialize(this , id, p);
144: } catch (FileNotFoundException ex) {
145: error("Unable to launch " + id
146: + ", no properties found:" + ex.getMessage());
147: return;
148: } catch (IOException ex) {
149: error("Unable to launch " + id
150: + ", IO error in reading props.");
151: return;
152: } catch (ClassNotFoundException ex) {
153: error("Unable to launch " + id + ": class " + cls
154: + " not found.");
155: return;
156: } catch (InstantiationException ex) {
157: error("Unable to launch " + id
158: + ": unable to instanciate " + cls);
159: return;
160: } catch (IllegalAccessException ex) {
161: error("Unable to launch " + id + ": illegal access to "
162: + cls);
163: return;
164: } catch (ServerHandlerInitException ex) {
165: error("Unable to launch " + id + ": " + ex.getMessage());
166: return;
167: }
168: } else {
169: // It may be a clone of some already existing server handler:
170: String clones = props.getString(id + "." + CLONES_P, null);
171: if (clones == null) {
172: error("Unable to launch " + id
173: + ": no class or clones defined.");
174: return;
175: }
176: // Lookup the server handler to be cloned, clone it
177: server = lookupServerHandler(clones);
178: if (server == null) {
179: error("Unable to launch " + id + ": " + clones
180: + " doesn't exit.");
181: return;
182: }
183: try {
184: server = server.clone(this , id, props
185: .loadPropertySpace(id));
186: } catch (Exception ex) {
187: error("Unable to launch " + id + ": clone failed on "
188: + server + "\r\n" + ex.getMessage());
189: return;
190: }
191: }
192: // Register the created server:
193: handlers.put(id, server);
194: }
195:
196: /**
197: * Lookup the server handler having the given identifier.
198: * @param id The identifier of the server handler to look for.
199: * @return A ServerHandler instance, or <strong>null</strong> if
200: * undefined.
201: */
202:
203: public ServerHandler lookupServerHandler(String id) {
204: return (ServerHandler) handlers.get(id);
205: }
206:
207: /**
208: * Enumerate all the server handler manager's identifiers.
209: * @return An enumeration of String.
210: */
211:
212: public Enumeration enumerateServerHandlers() {
213: return handlers.keys();
214: }
215:
216: /**
217: * Remove a server handler from this manager
218: * @param server, the Server Handler to remove
219: */
220: public void removeServerHandler(ServerHandler server) {
221: handlers.remove(server.getIdentifier());
222: }
223:
224: /**
225: * Create and initialize a fresh server handler manager.
226: * Each server handler declared in the properties is launched in turn.
227: * If no server handlers is declared, or if none of them is initializable
228: * the server manager is not created and a RuntimeException is thrown,
229: * otherwise, if at least one server handler was initialized, the server
230: * manager emits appropriate error messages to the error stream for each
231: * of the server handlers whose launch failed.
232: * @param props The properties this manager should be initialized from.
233: * @exception RuntimeException If no server handlers was declared through
234: * the properties.
235: */
236:
237: public ServerHandlerManager(String args[], File config, Properties p) {
238: // Initialize instance variables:
239: this .commandLine = args;
240: this .handlers = new Hashtable(7);
241: this .props = new DaemonProperties(config, p);
242: // Basically launch all requested servers, emiting errors if needed:
243: String hprop = props.getProperty(HANDLERS_P);
244: if ((hprop == null) || hprop.equals(""))
245: fatal("The property [org.w3c.jigsaw.daemon.handlers] is "
246: + " undefined.");
247: StringTokenizer st = new StringTokenizer(hprop, "|");
248: while (st.hasMoreTokens()) {
249: // Initialize and register this new server:
250: String id = st.nextToken();
251: launchServerHandler(id, props);
252: }
253: // Check that we launched at least one server
254: if (handlers.size() <= 0)
255: fatal("No servers initialized !");
256: // Terminate UNIX specific initialization:
257: unixStuff();
258: // add the shutdown hook, if we can!
259: Class c = java.lang.Runtime.class;
260: Class cp[] = { java.lang.Thread.class };
261: try {
262: Method m = c.getMethod("addShutdownHook", cp);
263: Runtime r = Runtime.getRuntime();
264: Object[] param = { new ServerShutdownHook(this ) };
265: m.invoke(r, param);
266: if (debug)
267: System.out
268: .println("*** shutdownHook succesfully registered");
269: } catch (NoSuchMethodException ex) {
270: // not using a recent jdk...
271: } catch (InvocationTargetException ex) {
272: if (debug)
273: ex.printStackTrace();
274: } catch (IllegalAccessException ex) {
275: if (debug)
276: ex.printStackTrace();
277: }
278: // now start the beast
279: Enumeration e = enumerateServerHandlers();
280: ServerHandler s;
281: while (e.hasMoreElements()) {
282: String id = (String) e.nextElement();
283: s = lookupServerHandler(id);
284: try {
285: s.start();
286: } catch (ServerHandlerInitException ex) {
287: error("Unable to start " + id + ": " + ex.getMessage());
288: s.shutdown();
289: removeServerHandler(s);
290: } catch (Exception ex) {
291: error("Unknown error while starting " + id + ": "
292: + ex.getMessage());
293: s.shutdown();
294: removeServerHandler(s);
295: }
296: }
297: if (handlers.size() <= 0)
298: fatal("No servers launched !");
299: }
300:
301: /**
302: * Shutdown this server handler manager.
303: * This method will call the shutdown method of all the running servers
304: * stopping the manager.
305: * <p>This server handler clones are considered shutdown too.
306: */
307: public synchronized void shutdown() {
308: Enumeration sk = handlers.keys();
309: ServerHandler server;
310: while (sk.hasMoreElements()) {
311: String servname = (String) sk.nextElement();
312: server = (ServerHandler) lookupServerHandler(servname);
313: server.shutdown();
314: }
315: }
316:
317: /**
318: * Do some UNIX specific initialization.
319: * THis method exists straight if it cannot succeed !
320: */
321:
322: public void unixStuff() {
323: // Do we have specific UNIX stuff in configuration ?
324: String user = props.getString(SERVER_USER_P, null);
325: String group = props.getString(SERVER_GROUP_P, null);
326: Unix unix = null;
327:
328: // Check that UNIX native methods are available:
329: if ((user != null) || (group != null)) {
330: unix = Unix.getUnix();
331: if (!unix.isResolved()) {
332: String msg = ("You used either the -user or -group command "
333: + " line option"
334: + " usefull only when running Jigsaw under UNIX."
335: + "To be able to set these, Jigsaw requires "
336: + " that libUnix, distributed with Jigsaw, "
337: + "be accessible from your LD_LIBRARY_PATH.");
338: fatal(msg);
339: }
340: }
341: // Change group if needed:
342: if (group != null) {
343: boolean error = false;
344: int gid = unix.getGID(group);
345: if (gid >= 0) {
346: try {
347: unix.setGID(gid);
348: } catch (UnixException ex) {
349: error = true;
350: }
351: }
352: if ((gid < 0) || error) {
353: String msg = ("UNIX initialization, unable to setgid(2) to "
354: + group
355: + ". Check the -group command line option." + ((gid < 0) ? "(the group doesn't exist)."
356: : "(setgid call failed)."));
357: fatal(msg);
358: }
359: System.out.println("+ running as group \"" + group + "\".");
360: }
361: // Change user if needed:
362: if (user != null) {
363: boolean error = false;
364: int uid = unix.getUID(user);
365: if (uid >= 0) {
366: try {
367: unix.setUID(uid);
368: } catch (UnixException ex) {
369: error = true;
370: }
371: }
372: if ((uid < 0) || error) {
373: String msg = ("UNIX initialization, unable to setuid(2) to "
374: + user
375: + ". Check the -user command line option." + ((uid < 0) ? "(the user doesn't exist)."
376: : "(setuid call failed)."));
377: fatal(msg);
378: }
379: System.out.println("+ running as user \"" + user + "\".");
380: }
381: }
382:
383: public static void usage() {
384: PrintStream o = System.out;
385: o.println("usage: httpd [OPTIONS]");
386: o.println("-root <directory> : root directory of server.");
387: o.println("-p <propfile> : property file to read.");
388: o.println("-trace : turns debugging on.");
389: o.println("-chroot <root> : chroot Jigsaw at startup.");
390: o
391: .println("-config <dir> : use given directory as config dir.");
392: o
393: .println("-user <user> : identity of the server (UNIX only).");
394: o
395: .println("-group <group> : group of the server (UNIX only).");
396: o
397: .println("-maxstores <int> : Max number of stores in memory.");
398: System.exit(1);
399: }
400:
401: public static void main(String args[]) {
402: Integer cmdport = null;
403: String cmdhost = null;
404: String cmdroot = null;
405: String cmdprop = "server.props";
406: String chroot = null;
407: Boolean cmdtrace = null;
408: String cmdconfig = null;
409: String cmduser = null;
410: String cmdgroup = null;
411: String maxstores = null;
412:
413: // Parse command line options:
414: for (int i = 0; i < args.length; i++) {
415: if (args[i].equals("-port")) {
416: try {
417: cmdport = new Integer(args[++i]);
418: } catch (NumberFormatException ex) {
419: System.out.println("invalid port number ["
420: + args[i] + "]");
421: System.exit(1);
422: }
423: } else if (args[i].equals("-host") && (i + 1 < args.length)) {
424: cmdhost = args[++i];
425: } else if (args[i].equals("-maxstores")
426: && (i + 1 < args.length)) {
427: maxstores = args[++i];
428: } else if (args[i].equals("-root") && (i + 1 < args.length)) {
429: cmdroot = args[++i];
430: } else if (args[i].equals("-p") && (i + 1 < args.length)) {
431: cmdprop = args[++i];
432: } else if (args[i].equals("-trace")) {
433: cmdtrace = Boolean.TRUE;
434: } else if (args[i].equals("-chroot")
435: && (i + 1 < args.length)) {
436: chroot = args[++i];
437: } else if (args[i].equals("-group")
438: && (i + 1 < args.length)) {
439: cmdgroup = args[++i];
440: } else if (args[i].equals("-user") && (i + 1 < args.length)) {
441: cmduser = args[++i];
442: } else if (args[i].equals("-config")
443: && (i + 1 < args.length)) {
444: cmdconfig = args[++i];
445: } else if (args[i].equals("?") || args[i].equals("-help")) {
446: usage();
447: } else {
448: System.out.println("unknown option: [" + args[i] + "]");
449: System.exit(1);
450: }
451: }
452: // Unix chroot if needed:
453: if (chroot != null) {
454: Unix unix = Unix.getUnix();
455: if (!unix.isResolved()) {
456: System.out
457: .println("You cannot chroot Jigsaw if the libUnix "
458: + " native extension is not available. "
459: + "Check the documentation, or remove "
460: + " this argument from command line.");
461: System.exit(1);
462: }
463: try {
464: unix.chroot(chroot);
465: } catch (Exception ex) {
466: System.out
467: .println("The chroot system call failed, details:");
468: ex.printStackTrace();
469: System.exit(1);
470: }
471: }
472: // Create the global daemon properties for all servers:
473: Properties props = System.getProperties();
474: if (cmdroot == null)
475: cmdroot = props.getProperty("user.dir", null);
476: File configdir = new File(cmdroot,
477: (cmdconfig == null) ? "config" : cmdconfig);
478: // Try to guess it, cause it is really required:
479: File propfile = new File(configdir, cmdprop);
480: System.out.println("loading properties from: "
481: + propfile.getAbsolutePath());
482: try {
483: props.load(new FileInputStream(propfile));
484: } catch (FileNotFoundException ex) {
485: System.out.println("Unable to load properties: " + cmdprop);
486: System.out.println("\t" + ex.getMessage());
487: System.exit(1);
488: } catch (IOException ex) {
489: System.out.println("Unable to load properties: " + cmdprop);
490: System.out.println("\t" + ex.getMessage());
491: System.exit(1);
492: }
493: // Override properties with our command line options:
494: if (cmdconfig != null)
495: props.put(httpd.CONFIG_P, new File(cmdroot, cmdconfig)
496: .getAbsolutePath());
497: if (cmdport != null)
498: props.put(httpd.PORT_P, cmdport.toString());
499: if (cmdhost != null)
500: props.put(httpd.HOST_P, cmdhost);
501: if (cmdroot != null)
502: props.put(httpd.ROOT_P, cmdroot);
503: if (cmdtrace != null) {
504: props.put(httpd.TRACE_P, "true");
505: props.put(httpd.CLIENT_DEBUG_P, "true");
506: }
507: if (maxstores != null)
508: props.put(httpd.MAX_LOADED_STORE_P, maxstores);
509: if (cmdgroup != null)
510: props.put(SERVER_GROUP_P, cmdgroup);
511: if (cmduser != null)
512: props.put(SERVER_USER_P, cmduser);
513: new ServerHandlerManager(args, configdir, props);
514: }
515:
516: }
|