// ServerHandlerManager.java
// $Id: ServerHandlerManager.java,v 1.24 2002/01/22 16:26:36 ylafon Exp $
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html
package org.w3c.jigsaw.daemon;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.StringTokenizer;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import org.w3c.util.ObservableProperties;
import org.w3c.util.Unix;
import org.w3c.util.UnixException;
import org.w3c.jigsaw.http.httpd;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
/**
* A ServerHandlerManager instance manages a set of ServerHandler.
*/
public class ServerHandlerManager {
private static final boolean debug = false;
/**
* The property containing the servers to be launched at startup time.
* This property is a <code>|</code> separated list of server identifiers.
* Declaring a server to this list requires that either:
* <ul>
* <li>An appropriate
* <code>org.w3c.jigsaw.daemon.</code><em>identifier</em>.<code>class
*</code>
* is declared and specify the class of the server to be launched (this
* class should implement the ServerHandler interface.).
* <li>An appropriate
* <code>org.w3c.jigsaw.daemon.</code><em>identifier</em>.<code>clones
*</code>
* is declared and its value specify an existing server to be cloned in
* order to create the new server.
* </ul>
*/
protected static final String HANDLERS_P ="org.w3c.jigsaw.daemon.handlers";
/**
* The server handler property <code>class</code> suffix.
*/
public final static String CLASS_P = "org.w3c.jigsaw.daemon.class";
/**
* The server handler property <code>clones</code> prefix.
*/
public final static
String CLONES_P = "org.w3c.jigsaw.daemon.clones";
public final static
String SERVER_USER_P = "org.w3c.jigsaw.daemon.user";
public final static
String SERVER_GROUP_P = "org.w3c.jigsaw.daemon.group";
/**
* The Application-Wide server manager.
*/
protected static ServerHandlerManager manager = null;
/**
* The list of running server handlers.
*/
protected Hashtable handlers = null;
/**
* The server handler manager property list.
*/
protected DaemonProperties props = null;
/**
* Command line options that were provided at launch time.
*/
protected String commandLine[] = null;
/**
* Emit a non-fatal error.
* @param msg The message to emit.
*/
protected void error(String msg) {
System.err.println(msg);
}
/**
* Emit a fatal error.
* This will abort the whole process !
* @param msg The fata error message.
*/
protected void fatal(String msg) {
System.out.println("*** FATAL Error:");
System.out.println(msg);
System.exit(1);
}
/**
* Get the command line options that were provided at launch time.
* @return A String array instance.
*/
public String[] getCommandLine() {
if ( commandLine == null )
commandLine = new String[0];
return commandLine;
}
/**
* For subclasses only. Used to update properties at runtime.
* This method is called by
* launchServerHandler(String id, DaemonProperties props).
* @param p the ServerHandlerManager properties.
* @see launchServerHandler
*/
protected void fixProperties(Properties p) {
//subclassed
}
/**
* Launch a new server handler.
* This method tries to launch a new server handler. If launching
* succeeds, it returns happily, otherwise, it emits an error message
* to the standard error stream.
* @param identifier The identifier of the server handler to be launched.
* @param props The properties from which the server handler should
* initialize itself.
*/
protected void launchServerHandler(String id, DaemonProperties props) {
ServerHandler server = null;
String cls = props.getString(id+"."+CLASS_P, null);
if ( cls != null ) {
// This is a fresh new server handler:
try {
Class c = Class.forName(cls);
server = (ServerHandler) c.newInstance();
ObservableProperties p = props.loadPropertySpace(id);
this.fixProperties(p);
server.initialize(this, id, p);
} catch (FileNotFoundException ex) {
error("Unable to launch "+id+", no properties found:"
+ ex.getMessage());
return;
} catch (IOException ex) {
error("Unable to launch "+id+", IO error in reading props.");
return;
} catch (ClassNotFoundException ex) {
error("Unable to launch "+id+": class "+cls+" not found.");
return;
} catch (InstantiationException ex) {
error("Unable to launch "+id+": unable to instanciate "+cls);
return;
} catch (IllegalAccessException ex) {
error("Unable to launch "+id+": illegal access to "+cls);
return;
} catch (ServerHandlerInitException ex) {
error("Unable to launch "+id+": "+ex.getMessage());
return;
}
} else {
// It may be a clone of some already existing server handler:
String clones = props.getString(id+"."+CLONES_P, null);
if ( clones == null ) {
error("Unable to launch "+id+": no class or clones defined.");
return;
}
// Lookup the server handler to be cloned, clone it
server = lookupServerHandler(clones);
if ( server == null ) {
error("Unable to launch "+id+": "+clones+" doesn't exit.");
return;
}
try {
server = server.clone(this, id, props.loadPropertySpace(id));
} catch (Exception ex) {
error("Unable to launch "+id+": clone failed on "+server+"\r\n"
+ ex.getMessage());
return;
}
}
// Register the created server:
handlers.put(id, server);
}
/**
* Lookup the server handler having the given identifier.
* @param id The identifier of the server handler to look for.
* @return A ServerHandler instance, or <strong>null</strong> if
* undefined.
*/
public ServerHandler lookupServerHandler(String id) {
return (ServerHandler) handlers.get(id);
}
/**
* Enumerate all the server handler manager's identifiers.
* @return An enumeration of String.
*/
public Enumeration enumerateServerHandlers() {
return handlers.keys();
}
/**
* Remove a server handler from this manager
* @param server, the Server Handler to remove
*/
public void removeServerHandler(ServerHandler server) {
handlers.remove(server.getIdentifier());
}
/**
* Create and initialize a fresh server handler manager.
* Each server handler declared in the properties is launched in turn.
* If no server handlers is declared, or if none of them is initializable
* the server manager is not created and a RuntimeException is thrown,
* otherwise, if at least one server handler was initialized, the server
* manager emits appropriate error messages to the error stream for each
* of the server handlers whose launch failed.
* @param props The properties this manager should be initialized from.
* @exception RuntimeException If no server handlers was declared through
* the properties.
*/
public ServerHandlerManager (String args[], File config, Properties p) {
// Initialize instance variables:
this.commandLine = args;
this.handlers = new Hashtable(7);
this.props = new DaemonProperties(config, p);
// Basically launch all requested servers, emiting errors if needed:
String hprop = props.getProperty(HANDLERS_P);
if ((hprop == null) || hprop.equals(""))
fatal("The property [org.w3c.jigsaw.daemon.handlers] is "+
" undefined.");
StringTokenizer st = new StringTokenizer(hprop, "|");
while (st.hasMoreTokens()) {
// Initialize and register this new server:
String id = st.nextToken();
launchServerHandler(id, props);
}
// Check that we launched at least one server
if ( handlers.size() <= 0 )
fatal("No servers initialized !");
// Terminate UNIX specific initialization:
unixStuff();
// add the shutdown hook, if we can!
Class c = java.lang.Runtime.class;
Class cp[] = { java.lang.Thread.class };
try {
Method m = c.getMethod("addShutdownHook", cp);
Runtime r = Runtime.getRuntime();
Object[] param = { new ServerShutdownHook(this) };
m.invoke(r, param);
if (debug)
System.out.println("*** shutdownHook succesfully registered");
} catch (NoSuchMethodException ex) {
// not using a recent jdk...
} catch (InvocationTargetException ex) {
if (debug)
ex.printStackTrace();
} catch (IllegalAccessException ex) {
if (debug)
ex.printStackTrace();
}
// now start the beast
Enumeration e = enumerateServerHandlers();
ServerHandler s;
while (e.hasMoreElements()) {
String id = (String) e.nextElement();
s = lookupServerHandler(id);
try {
s.start();
} catch (ServerHandlerInitException ex) {
error("Unable to start "+id+": "+ex.getMessage());
s.shutdown();
removeServerHandler(s);
} catch (Exception ex) {
error("Unknown error while starting "+id+": "+ex.getMessage());
s.shutdown();
removeServerHandler(s);
}
}
if ( handlers.size() <= 0 )
fatal("No servers launched !");
}
/**
* Shutdown this server handler manager.
* This method will call the shutdown method of all the running servers
* stopping the manager.
* <p>This server handler clones are considered shutdown too.
*/
public synchronized void shutdown() {
Enumeration sk = handlers.keys();
ServerHandler server;
while (sk.hasMoreElements()) {
String servname = (String) sk.nextElement();
server = (ServerHandler) lookupServerHandler(servname);
server.shutdown();
}
}
/**
* Do some UNIX specific initialization.
* THis method exists straight if it cannot succeed !
*/
public void unixStuff() {
// Do we have specific UNIX stuff in configuration ?
String user = props.getString(SERVER_USER_P, null);
String group = props.getString(SERVER_GROUP_P, null);
Unix unix = null;
// Check that UNIX native methods are available:
if ((user != null) || (group != null)) {
unix = Unix.getUnix();
if ( ! unix.isResolved() ) {
String msg = ("You used either the -user or -group command "
+ " line option"
+ " usefull only when running Jigsaw under UNIX."
+ "To be able to set these, Jigsaw requires "
+ " that libUnix, distributed with Jigsaw, "
+ "be accessible from your LD_LIBRARY_PATH.");
fatal(msg);
}
}
// Change group if needed:
if (group != null) {
boolean error = false;
int gid = unix.getGID(group);
if (gid >= 0) {
try {
unix.setGID(gid);
} catch (UnixException ex) {
error = true;
}
}
if ((gid < 0) || error) {
String msg = ("UNIX initialization, unable to setgid(2) to "
+ group
+ ". Check the -group command line option."
+ ((gid < 0)
? "(the group doesn't exist)."
: "(setgid call failed)."));
fatal(msg);
}
System.out.println("+ running as group \""+group+"\".");
}
// Change user if needed:
if (user != null) {
boolean error = false;
int uid = unix.getUID(user);
if (uid >= 0) {
try {
unix.setUID(uid);
} catch (UnixException ex) {
error = true;
}
}
if ((uid < 0) || error) {
String msg = ("UNIX initialization, unable to setuid(2) to "
+ user
+ ". Check the -user command line option."
+ ((uid < 0)
? "(the user doesn't exist)."
: "(setuid call failed)."));
fatal(msg);
}
System.out.println("+ running as user \""+user+"\".");
}
}
public static void usage () {
PrintStream o = System.out ;
o.println("usage: httpd [OPTIONS]") ;
o.println("-root <directory> : root directory of server.") ;
o.println("-p <propfile> : property file to read.");
o.println("-trace : turns debugging on.") ;
o.println("-chroot <root> : chroot Jigsaw at startup.");
o.println("-config <dir> : use given directory as config dir.");
o.println("-user <user> : identity of the server (UNIX only).");
o.println("-group <group> : group of the server (UNIX only).");
o.println("-maxstores <int> : Max number of stores in memory.") ;
System.exit (1) ;
}
public static void main (String args[]) {
Integer cmdport = null ;
String cmdhost = null ;
String cmdroot = null ;
String cmdprop = "server.props";
String chroot = null;
Boolean cmdtrace = null ;
String cmdconfig = null;
String cmduser = null;
String cmdgroup = null;
String maxstores = null;
// Parse command line options:
for (int i = 0 ; i < args.length ; i++) {
if ( args[i].equals ("-port") ) {
try {
cmdport = new Integer(args[++i]) ;
} catch (NumberFormatException ex) {
System.out.println ("invalid port number ["+args[i]+"]");
System.exit (1) ;
}
} else if ( args[i].equals ("-host") && (i+1 < args.length)) {
cmdhost = args[++i] ;
} else if ( args[i].equals ("-maxstores") && (i+1 < args.length)) {
maxstores = args[++i];
} else if ( args[i].equals ("-root") && (i+1 < args.length)) {
cmdroot = args[++i] ;
} else if ( args[i].equals ("-p") && (i+1 < args.length)) {
cmdprop = args[++i] ;
} else if ( args[i].equals ("-trace") ) {
cmdtrace = Boolean.TRUE;
} else if ( args[i].equals("-chroot") && (i+1 < args.length)) {
chroot = args[++i];
} else if ( args[i].equals("-group") && (i+1 < args.length)) {
cmdgroup = args[++i];
} else if ( args[i].equals("-user") && (i+1 < args.length)) {
cmduser = args[++i];
} else if ( args[i].equals("-config") && (i+1 < args.length)) {
cmdconfig = args[++i];
} else if ( args[i].equals ("?") || args[i].equals ("-help") ) {
usage() ;
} else {
System.out.println ("unknown option: ["+args[i]+"]") ;
System.exit (1) ;
}
}
// Unix chroot if needed:
if ( chroot != null ) {
Unix unix = Unix.getUnix();
if ( ! unix.isResolved() ) {
System.out.println("You cannot chroot Jigsaw if the libUnix "
+ " native extension is not available. "
+ "Check the documentation, or remove "
+ " this argument from command line.");
System.exit(1);
}
try {
unix.chroot(chroot);
} catch (Exception ex) {
System.out.println("The chroot system call failed, details:");
ex.printStackTrace();
System.exit(1);
}
}
// Create the global daemon properties for all servers:
Properties props = System.getProperties();
if (cmdroot == null)
cmdroot = props.getProperty("user.dir", null);
File configdir = new File(cmdroot
, (cmdconfig==null) ? "config" : cmdconfig);
// Try to guess it, cause it is really required:
File propfile = new File (configdir, cmdprop);
System.out.println ("loading properties from: "
+ propfile.getAbsolutePath()) ;
try {
props.load(new FileInputStream(propfile)) ;
} catch (FileNotFoundException ex) {
System.out.println ("Unable to load properties: "+cmdprop);
System.out.println ("\t"+ex.getMessage()) ;
System.exit (1) ;
} catch (IOException ex) {
System.out.println ("Unable to load properties: "+cmdprop);
System.out.println ("\t"+ex.getMessage()) ;
System.exit (1) ;
}
// Override properties with our command line options:
if ( cmdconfig != null )
props.put(httpd.CONFIG_P
, new File(cmdroot, cmdconfig).getAbsolutePath());
if ( cmdport != null )
props.put (httpd.PORT_P, cmdport.toString()) ;
if ( cmdhost != null )
props.put (httpd.HOST_P, cmdhost) ;
if ( cmdroot != null )
props.put (httpd.ROOT_P, cmdroot) ;
if ( cmdtrace != null ) {
props.put (httpd.TRACE_P, "true") ;
props.put (httpd.CLIENT_DEBUG_P, "true") ;
}
if (maxstores != null)
props.put (httpd.MAX_LOADED_STORE_P, maxstores);
if ( cmdgroup != null )
props.put(SERVER_GROUP_P, cmdgroup);
if ( cmduser != null )
props.put(SERVER_USER_P, cmduser);
new ServerHandlerManager(args, configdir, props);
}
}