// httpd.java
// $Id: httpd.java,v 1.57 1997/09/01 07:48:09 ylafon Exp $
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html


package w3c.jigsaw.http ;

import java.io.* ;
import java.util.*;
import java.net.*;

import w3c.tools.store.*;
import w3c.tools.timers.* ;

// FIXME should be described in an interface in http
import w3c.jigsaw.resources.* ;

import w3c.jigsaw.indexer.* ;
import w3c.jigsaw.auth.*;
import w3c.jigsaw.daemon.*;
import w3c.www.http.*;
import w3c.jigsaw.config.*;
import w3c.util.*;

class DummyResourceStoreHolder implements ResourceStoreHolder {

    public boolean acceptStoreUnload(ResourceStore store) {
	return false ;
    }

    public void notifyStoreShutdown(ResourceStore store) {
	return ;
    }

    public void notifyStoreStabilize(ResourceStore store) {
	return;
    }
    

}

/**
 * <p>The server main class. This class can be used either through its
 * main method, to run a full httpd server, or simply by importing it
 * into your app. This latter possibility allows you to export some of
 * your application state through http.
 *
 * <p>The server itself uses this to report about memory consumption,
 * running threads, etc.
 */

public class httpd implements 
      ServerHandler, Runnable, PropertyMonitoring, Cloneable
{
    /**
     * The current displayed version of Jigsaw.
     */
    public static final String version = "1.0beta1";
    /**
     * The current internal version counter of Jigsaw.
     * This counter is bumped anytime the configuration needs upgrade.
     */
    public static final int verscount = 3;

    public static final
    String VERSCOUNT_P = "w3c.jigsaw.version.counter";
    /**
     * Name of the server software property.
     * The server software is the string that gets emited by Jigsaw 
     * on each reply, to tell the client what server emited the reply.
     * <p>This property defaults to <strong>Jigsaw/1.0a</strong>.
     */
    public static final 
    String SERVER_SOFTWARE_P = "w3c.jigsaw.server";
    /**
     * Name of the server host property.
     * The host property should be set to the name of the host running
     * this server.
     * <p>This property defaults to the local host name, although if you want
     * directory listing to work propertly, you might need to provide the 
     * full host name (including its domain).
     */
    public static final String HOST_P            = "w3c.jigsaw.host" ;
    /**
     * Name of the property giving the server root directory.
     * <p>The server root directory is used to deduce a bunch of defaults
     * properties, when they don't have any specific values.
     * <p>This property has no defaults.
     */
    public static final String ROOT_P = "w3c.jigsaw.root" ;
    /**
     * Name of the property giving the server's config directory.
     */
    public static final String CONFIG_P = "w3c.jigsaw.config";
    /**
     * Name of the property giving the server space directory.
     * The server space directory should contain an index file, built
     * with the indexer.
     * <p>This property defaults to <w3c.jigsaw.root>/WWW.
     */
    public static final String SPACE_P            = "w3c.jigsaw.space" ;
    /**
     * Name of the server port property.
     * At initializatiojn time, the server will bind its accepting socket
     * to the host its runs on, and to the provided port.
     * <p>This property defaults to <code>8888</code>.
     */
    public static final String PORT_P            = "w3c.jigsaw.port" ;
    /**
     * Name of the server's trace property.
     * When set to true, the server will emit some traces indicating 
     * its current state by using the logger <em>trace</em> methods.
     * This property should be set to <string>true</strong> if you want
     * clients to emit traces.
     * <p>This property defaults to <strong>false</strong>.
     */
    public static final String TRACE_P           = "w3c.jigsaw.trace" ;
    /**
     * Name of the server's keep alive flag.
     * This property is used to determine wether this server should keep
     * its connection alive. Keeping connection alive requires this flag
     * to set to <strong>true</strong>, and clients to be compliant to the
     * keep alive feature as described in HTTP/1.1 specification.
     * <p>This property defaults to <strong>true</strong>.
     */
    public static final String KEEP_ALIVE_P      = "w3c.jigsaw.keepAlive" ;
    /**
     * Name of the server's connection time out property.
     * This property gives, in milliseconds, the timeout to use for
     * connections that remains idel, waiting for an incoming request.
     * <p>This property defaults to <code>10000</code> milliseconds.
     */
    public static final String KEEP_TIMEOUT_P="w3c.jigsaw.keep_alive.timeout";
    /**
     * Name of the server's request time out property.
     * The request time out property value indicates, in milliseconds, the
     * allowed duration of a request. Any request whose duration exceeds
     * this time out value will be aborted.
     * <p>This property defaults to <code>60000</code>.
     */
    public static final String REQUEST_TIMEOUT_P="w3c.jigsaw.request.timeout";
    /**
     * Name of the client thread priority property.
     * Every client threads will run at the given priority, which should be
     * in the range of valid threads priority.
     * <p>This property defaults to <code>Thread.NORM_PRIORITY</code>.
     */
    public static final String CLIENT_PRIORITY_P="w3c.jigsaw.client.priority";
    /**
     * Nam eof the property giving the client output buffer size.
     * Each clients, when not using a shuffler, has to allocate its own
     * output buffer, Output buffer size may increase/decrease significantly
     * the Jigsaw performances, so change it with care.
     * <p>This property defaults to <code>8192</code>.
     */
    public static final String CLIENT_BUFSIZE_P="w3c.jigsaw.client.bufsize";
    /**
     * Name of the property indicating wether client should be debuged.
     * When debuged, clients emit some traces, through the server logger
     * about their current state.
     * <p>This property defaults to <strong>false</strong>.
     */
    public static final String CLIENT_DEBUG_P="w3c.jigsaw.client.debug" ;
    /**
     * Name of  property that indicates if some security manager is required.
     * You usually don't want to run a security manager for the server, 
     * except in the unlikely (right now) case that you want the server to
     * be able to host agents.
     * <p>This property defaults to <string>false</strong>.
     */
    public static final String USE_SM_P = "w3c.http.useSecurityManager" ;
    /**
     * Name of property indicating the logger class to use.
     * The Jigsaw server allows you to implement your own logger. The only
     * logger provided with the core server is the 
     * <code>w3c.jigsaw.core.CommonLogger</code>, which implements the
     * common log format.
     * <p>This property defaults to <code>w3c.jigsaw.core.CommonLogger</code>.
     */
    public static final String LOGGER_P = "w3c.jigsaw.logger" ;
    /**
     * Name of the property indicating the client factory class.
     */
    public static final String 
    CLIENT_FACTORY_P = "w3c.jigsaw.http.ClientFactory";
    /**
     * Name of the property giving the shuffler path.
     * This property should be set if you are to use the shuffler. The 
     * data shuffler is an external process to whiuch Jigsaw delegates 
     * the task of writing back document content to clients. Use this
     * when you think your server isn't fast enough.
     * <p>This should be an absloute path.
     * <p>This property has no defaults.
     */
    public static final String SHUFFLER_PATH_P   = "w3c.jigsaw.shuffler.path";
    /**
     * Name of the property giving the root store of the server.
     * The root store is the repository for the pickled version of the root 
     * resource. 
     * <p>This property defaults to the <code>.jigidx</code> file under
     * the space directory.
     */
    public static final String ROOT_STORE_P = "w3c.jigsaw.root.store";
    /**
     * Name of the property giving the name of the root resource.
     * Upon startup, or restart, the server will look in its root store
     * a resource whose name is given by this resource, and install it as
     * its root resource.
     * <p>This property defaults to <code>root</code>.
     */
    public static final String ROOT_NAME_P = "w3c.jigsaw.root.name" ;
    /**
     * Name of the property giving the path of the property file.
     * this should be used internally (for restart) only.
     * <p>This property defaults to <code>config/httpd.props</code>.
     */
    public static final String PROPS_P = "w3c.jigsaw.propfile" ;
    /**
     * Name of the property indicating if the file-system is case sensitive.
     * This property determines wether Jigsaw will list all files to check 
     * for case sensitivity, before creating a resource for that file.
     * <p>For obvious security reasons, this property defaults to 
     * <strong>true</strong>.
     */
    public static final 
    String FS_SENSITIVITY = "w3c.jigsaw.checkSensitivity";
    /**
     * Name of the property indicating the URL of Jigsaw's help.
     * This URL should point to the URL path of Jigsaw's documentation
     * as served by that server.
     */
    public static
    String DOCURL_P = "w3c.jigsaw.docurl";
    /**
     * Name of the property indicating the public methods allowed on that 
     * server.
     * This property should provide a <code>|</code> separated list of
     * methods available on that server.
     * <p>This property defaults to: <strong>GET | HEAD | PUT | POST 
     * | OPTIONS | DELETE | LINK | UNLINK</code>.
     */
    public static 
    String PUBLIC_P = "w3c.jigsaw.publicMethods";
    /**
     * Name of the property that indicates the root resource for edit.
     * The edit root resource is the one that will show up by default
     * when accessing the FormResourceEditor.
     * @see w3c.jigsaw.formedit.FormResourceEditor
     */
    public static 
    String EDIT_ROOT_P = "w3c.jigsaw.edit.root";
    /**
     * UNIX - Name of the property that indicates the server user.
     * When set, the server will try to turn itself to the given user name
     * after initialization. If this fail, the server will abort.
     * <p>This property has no default value.
     */
    public static
    String SERVER_USER_P = "w3c.jigsaw.unix.user";
    /**
     * UNIX - Name of the property that indicates the server group.
     * When set, the server will try to turn itself to the given group name
     * after initialization. If this fail, the server will abort.
     * <p>This property has no default value.
     */
    public static
    String SERVER_GROUP_P = "w3c.jigsaw.unix.group";

    
    /**
     * The list of currently running servers.
     */
    private static Hashtable servers = new Hashtable() ;

    /* FIXME */ public Thread thread    = null ;

    private String         software  = "Jigsaw/1.0a";
    private ServerSocket   socket    = null ;
    private Logger         logger    = null ;
    private Shuffler       shuffler  = null ;
    public  EventManager   timer     = null ;
    ClientFactory          factory   = null ;
    
    /**
     * The (optional) server handler manager that created us.
     */
    private ServerHandlerManager shm = null;
    /**
     * The server identifier can be any String.
     * This identifier is used by the configuration applets, to show all the 
     * running servers in the process, and to edit their properties, etc.
     */
    private String identifier = null ;

    /**
     * This server statistics object.
     */
    private httpdStatistics statistics = null ;
    /**
     * This server set of properties.
     */
    private ObservableProperties props    = null ;
    /** 
     * Should the server run the server in trace mode ?
     */
    private boolean tracep = false ;
    /**
     * Should the server try to keep connections alive ?
     */
    private boolean keep = true ; 
    /**
     * What logger class should the server use to log accesses.
     */
    private String logger_class = null ;
    /**
     * What client factory class should the server use.
     */
    private String 
    factory_class = "w3c.jigsaw.http.socket.SocketClientFactory";
    /**
     * The coordinate of the shuffler, or <strong>null</strong> is none is to 
     * be used. 
     */
    private String shuffler_path = null ;
    /**
     * The server's root directory.
     */
    private File root_dir = null ;
    /**
     * The directory containing the server exported documents.
     */
    private File space_dir = null ;
    /**
     * The server host name.
     */
    private String host = null ;
    /**
     * The server port.
     */
    private int port = 8001 ;
    /**
     * This server client debug flag.
     */
    private boolean client_debug = false ;
    /**
     * This server's request time slice, in milliseconds.
     */
    private int request_time_out = 1200000 ;
    /**
     * This server's connection allowed idle time in milliseconds.
     */
    private int connection_time_out = 1200000 ;
    /**
     * This server's client thread priority.
     */
    private int client_priority = Thread.NORM_PRIORITY ;
    /**
     * This server's clients buffer size.
     */
    private int client_bufsize = 4096 ;
    /**
     * Is the file system case-sensitive ?
     */
    private boolean sensitivity = true;
    /**
     * This server root entity.
     */
    public HTTPResource root = null ;
    /**
     * This server URL.
     */
    private URL url = null ;
    /**
     * Finishing (killing) the server.
     */
    private boolean finishing = false ;
    /**
     * Finishing, but restart straight up.
     */
    private boolean restarting = false ;
    /**
     * The indexer attached to this server.
     */
    private ResourceIndexer indexer = null ;
    /**
     * The realm catalog
     */
    private RealmsCatalog realms = null ;
    /**
     * The resource store manager for this server.
     */
    private ResourceStoreManager manager = null ;
    /**
     * The root store file repository.
     */
    private File root_repository = null ;
    /**
     * The loaded root store.
     */
    private ResourceStore root_store = null;
    /**
     * The root resource's identifier.
     */
    private String root_name = null ;
    /**
     * The full URL of Jigsaw's documentation as served by that server.
     */
    private String docurl = null;
    /**
     * The list of public methods for that server.
     */
    private  String publicMethods[] = { "GET"
					, "HEAD"
					, "PUT"
					, "POST"
					, "LINK"
					, "UNLINK"
					, "DELETE"
					, "OPTIONS" 
    } ;
    /**
     * The <code>Public</code> header value, computed out of the
     * <code>publicMethods</code> variable.
     */
    private HttpTokenList publicHeader = null;
    /**
     * The edit root for that server.
     */
    private HTTPResource editroot = null;

    /**
     * The property monitoring implementation.
     * @param name The name of the property that has changed.
     * @return A boolean, <strong>true</strong> if the changed was taken into
     *    account, <strong>false</strong> otherwise.
     */

    public boolean propertyChanged (String name) {
	// Is this a property we are interested in ?
	if ( name.equals(SERVER_SOFTWARE_P) ) {
	    software = props.getString(name, software);
	    return true;
	} else if ( name.equals(TRACE_P) ) {
	    tracep = props.getBoolean(name, tracep) ;
	    errlog (name + " changed to " + tracep) ;
	    return true ;
	} else if ( name.equals(KEEP_ALIVE_P) ) {
	    keep = props.getBoolean (name, keep) ;
	    errlog (name + " changed to " + keep) ;
	    return true ;
	} else if ( name.equals(LOGGER_P) ) {
	    errlog (name + " change failed (server running)") ;
	    return false ;
	} else if ( name.equals(ROOT_NAME_P) ) {
	    String newname = props.getString(name, null);
	    if ( changeRoot(newname) != null ) {
		errlog("new root resource ["+newname+"]");
		return true;
	    } else {
		errlog("failed to change root to ["+newname+"].");
		return false;
	    }
	} else if ( name.equals(SPACE_P) ) {
	    errlog (name + " change failed (server running)") ;
	    return false ;
	} else if ( name.equals(HOST_P) ) {
	    errlog (name + " change failed (server running)") ;
	    return false ;
	} else if ( name.equals(PORT_P) ) {
	    errlog (name + " change failed (server running)") ;
	    return false ;
	} else if ( name.equals(CLIENT_DEBUG_P) ) {
	    client_debug = props.getBoolean(name, client_debug) ;
	    errlog (name + " changed to " + client_debug) ;
	    return true ;
	} else if ( name.equals(REQUEST_TIMEOUT_P) ){
	    request_time_out = props.getInteger(name, request_time_out);
	    errlog (name + " changed to " + request_time_out) ;
	    return true ;
	} else if ( name.equals(KEEP_TIMEOUT_P) ) {
	    connection_time_out = props.getInteger(name
						   , connection_time_out);
	    errlog (name + " changed to " + connection_time_out) ;
	    return true ;
	} else if ( name.equals(CLIENT_PRIORITY_P) ){
	    client_priority = props.getInteger (name, client_priority) ;
	    errlog (name + " changed to " + client_priority) ;
	    return true ;
	} else if ( name.equals(CLIENT_BUFSIZE_P) ){
	    client_bufsize = props.getInteger (name, client_bufsize) ;
	    errlog (name + " changed to " + client_bufsize) ;
	    return true ;
	} else if ( name.equals(DOCURL_P) ) {
	    String propval = props.getString(name, docurl);
	    try {
		URL u  = new URL(getURL(), propval);
		docurl = u.toExternalForm();
	    } catch (Exception ex) {
		return false;
	    }
	    return true;
	} else if ( name.equals(PUBLIC_P) ) {
	    publicMethods = props.getStringArray(name, publicMethods);
	    publicHeader  = null;
	    return true;
	} else if ( name.equals(SERVER_USER_P) ) {
	    String user = props.getString(SERVER_USER_P, null);
	    errlog("new user: "+user);
	    return false;
	} else if (name.equals(SERVER_GROUP_P) ) {
	    String group = props.getString(SERVER_GROUP_P, null);
	    errlog("new group: "+group);
	    return false;
	} else {
	    // We  don't care about this one
	    return true ;
	}
    }

    /**
     * Initialize this server indexer.
     */

    private void initializeIndexer() {
	ResourceContext c  = getDefaultContext();
	IndexersCatalog ic = getIndexersCatalog();
	IndexerModule   m  = new IndexerModule(ic);
	// Register the default indexer:
	m.registerIndexer(c, "default");
	// Register the indexer module:
	c.registerModule(IndexerModule.NAME, m);
    }

    /**
     * Initialize the resource store manager for this server.
     */

    private void initializeResourceStoreManager() {
	this.manager = new ResourceStoreManager(this.getStoreDirectory());
    }

    /**
     * Lookup the root store for some resource.
     * @param name The name of the resource to lookup in the root store.
     * @return The loaded resource, or <strong>null</strong>.
     */

    public HTTPResource loadResource(String name) {
	Hashtable defs = new Hashtable(11) ;
	defs.put("resource-store", root_store) ;
	defs.put("url", "/"+name);
	defs.put("directory", space_dir) ;
	defs.put("context", getDefaultContext()) ;
	try {
	    return (HTTPResource) root_store.loadResource(name, defs);
	} catch (InvalidResourceException ex) {
	    return null;
	}
    }

    /**
     * Checkpoint all cached data, by saving them to disk.
     */

    public void checkpoint() {
	manager.checkpoint();
    }

    /**
     * Dynamically change the root resource for the server.
     * This is kind a dangerous operation !
     * @param name The name of the new root resource, to be found in the
     * root resource store.
     * @return The new installed root resource, or <strong>null</strong>
     * if we couldn't load the given resource.
     */

    public synchronized HTTPResource loadRoot(String name) {
	HTTPResource newroot = null;
	// Restore the appropriate root resource:
	Hashtable defs = new Hashtable(11) ;
	defs.put("resource-store", root_store) ;
	defs.put("url", "/");
	defs.put("directory", space_dir) ;
	defs.put("context", getDefaultContext()) ;
	try {
	    newroot = (HTTPResource) root_store.loadResource(name,defs);
	} catch (Exception ex) {
	    ex.printStackTrace();
	    String err = ("Unable to restore root resource ["+root_name+"]"
			  +" from store ["+root_repository+"]"
			  +"\r\ndetails:\r\n"+ex.getMessage());
	    errlog(err);
	}
	return newroot;
    }

    private synchronized HTTPResource changeRoot(String name) {
	HTTPResource newroot = loadRoot(name);
	if ( newroot != null ) {
	    this.root      = newroot;
	    this.root_name = name;
	}
	return newroot;
    }

    /**
     * Initialize this server's root resource.
     */

    private void initializeRootResource() 
	throws ServerHandlerInitException
    {
	ResourceStoreHolder dummy = new DummyResourceStoreHolder() ;
	// Load in the resource store:
	root_store = manager.loadResourceStore(dummy, root_repository);
	// Check for un-found root resource:
	if ( changeRoot(root_name) == null ) {
	    String err = ("Unable to restore root resource ["+root_name+"]"
			  +" from store ["+root_repository+"] (not found).");
	    throw new ServerHandlerInitException(err);
	}
    }

    /**
     * Initialize the realms catalog for this server.
     */

    private void initializeRealmsCatalog() {
	this.realms = new RealmsCatalog(this);
    }

    /**
     * Initialize the server logger and the statistics object.
     */

    private void initializeLogger() 
	throws ServerHandlerInitException
    {
	if ( logger_class != null ) {
	    try {
		logger = (Logger) Class.forName(logger_class).newInstance() ;
		logger.initialize (this) ;
	    } catch (Exception ex) {
		String err = ("Unable to create logger of class ["
			      + logger_class +"]"
			      + "\r\ndetails: \r\n"
			      + ex.getMessage());
		throw new ServerHandlerInitException(err);
	    }
	} else {
	    warning ("no logger specified, not logging.");
	}
	// Initialize the statistics object:
	statistics = new httpdStatistics(this) ;
    }

    /**
     * Initialize the server socket, create a suitable client factory, start.
     * This method creates the physicall listening socket, and instantiate
     * an appropriate client factory for that socket. It then run the accept
     * thread, ready to accept new incomming connections.
     */

    private void initializeServerSocket() 
	throws ServerHandlerInitException
    {
	// Create a suitable client factory:
	try {
	    Class c = Class.forName(factory_class);
	    factory = (ClientFactory) c.newInstance();
	    factory.initialize(this);
	} catch (Exception ex) {
	    String err = ("Unable to create a client factory of class "
			  + "\"" + factory_class + "\""
			  + " details: \r\n" + ex.getMessage());
	    throw new ServerHandlerInitException(err);
	}
	// If needed, create a server socket instance for that context:
	try {
	    socket = factory.createServerSocket();
	} catch (IOException ex) {
	    String err = ("Unable to create server socket on port "+port
			  + ": " + ex.getMessage() + ".");
	    throw new ServerHandlerInitException(err);
	}
	this.thread   = new Thread (this) ;
	this.thread.setName(identifier) ;
	this.thread.setPriority (Thread.MAX_PRIORITY) ;
    }
     
    /**
     * Initialize our event manager.
     */

    private void initializeEventManager() {
	this.timer   = new EventManager () ;
	this.timer.setDaemon(true);
	this.timer.start() ;
    }

    /**
     * Initialize some of the servers instance values from properties.
     */

    private void initializeProperties() 
	throws ServerHandlerInitException
    {
	// Compute some default values (host and port)
	String defhost  = null ;
	String rootstr  = null ;
	String spacestr = null ;
	String storestr = null ;
	try {
	    defhost = InetAddress.getLocalHost().getHostName() ;
	} catch (UnknownHostException e) {
	    defhost = null ;
	}
	// Second stage: get property values:
	software       = props.getString(SERVER_SOFTWARE_P, software);
	tracep         = props.getBoolean(TRACE_P,tracep) ;
	keep           = props.getBoolean(KEEP_ALIVE_P,keep) ;
	logger_class   = props.getString(LOGGER_P, null) ;
	factory_class  = props.getString(CLIENT_FACTORY_P,factory_class);
	shuffler_path  = props.getString(SHUFFLER_PATH_P, null) ;
	rootstr        = props.getString(ROOT_P, null) ;
	spacestr       = props.getString(SPACE_P, null);
	host           = props.getString(HOST_P, defhost) ;
	port           = props.getInteger(PORT_P, port) ;
	storestr       = props.getString(ROOT_STORE_P, null) ;
	root_name      = props.getString(ROOT_NAME_P, "root") ;
	sensitivity    = props.getBoolean(FS_SENSITIVITY, true);
	publicMethods  = props.getStringArray(PUBLIC_P, publicMethods);
	// Get client properties:
	client_debug        = props.getBoolean (CLIENT_DEBUG_P, client_debug) ;
	request_time_out    = props.getInteger (REQUEST_TIMEOUT_P
						, request_time_out);
	connection_time_out = props.getInteger (KEEP_TIMEOUT_P
						, connection_time_out);
	client_priority     = props.getInteger(CLIENT_PRIORITY_P
					       , client_priority);
	client_bufsize      = props.getInteger(CLIENT_BUFSIZE_P
					       , client_bufsize);
	// Check that a host name has been given:
	if ( host == null )
	    throw new ServerHandlerInitException(this.getClass().getName()
						 +"[initializeProperties]: "
						 +"[host] undefined.");
	// Default the root directory to the current directory:
	if ( rootstr == null ) {
	    // Try the current directory as root:
	    rootstr = System.getProperties().getProperty("user.dir", null) ;
	    if ( rootstr == null )
		throw new ServerHandlerInitException(this.getClass().getName()
						     +"[initializeProperties]:"
						     +"[root] undefined.");
	}
	root_dir = new File(rootstr) ;
	// Default the space directory to root/WWW
	if ( spacestr == null ) 
	    space_dir = new File(root_dir, "WWW") ;
	else
	    space_dir = new File(spacestr) ;
	// Default the root store to its value:
	if ( storestr == null ) 
	    root_repository = new File(getStoreDirectory(), "root.idx") ;
	else
	    root_repository = new File(space_dir, storestr) ;
	// Help URL:
	String propval = props.getString(DOCURL_P, null);
	if ( propval != null ) {
	    try {
		URL u  = new URL(getURL(), propval);
		docurl = u.toExternalForm();
	    } catch (Exception ex) {
	    }
	}
    }

    /**
     * Register the given object as a property ediotr, for this server.
     * @param propedit The property editor.
     */

    PropertyEditor propEditor = null;
    Vector         propSet    = new Vector(8);

    public synchronized
    void registerPropertyEditor(PropertyEditor propEditor) {
	this.propEditor = propEditor;
	// Register the sets we know about that had no editors.
	if ( propSet != null ) {
	    for (int i = 0 ; i < propSet.size() ; i++) {
		PropertySet set = (PropertySet) propSet.elementAt(i);
		propEditor.registerPropertySet(set);
	    }
	}
    }

    /**
     * Register a property set to the server.
     * @param propSet The property set to register.
     */

    public synchronized 
    void registerPropertySet(PropertySet set) {
	// Add this set to our known property set:
	propSet.addElement(set);
	// If an editor has been registered, notify it:
	if ( propEditor != null ) 
	    propEditor.registerPropertySet(set);
    }

    public Enumeration enumeratePropertySet() {
	return propSet.elements();
    }

    public Resource getPropertySet(String name) {
	for (int i = 0 ; i < propSet.size() ; i++) {
	    PropertySet set = (PropertySet) propSet.elementAt(i);
	    if ( set.getIdentifier().equals(name) )
		return set;
	}
	return null;
    }

    protected void initializePropertySets() {
	registerPropertySet(new GeneralProp("general", this));
	registerPropertySet(new ConnectionProp("connection", this));
	registerPropertySet(new LoggingProp("logging", this));
    }

    /**
     * Get this server statistics.
     */

    public httpdStatistics getStatistics() {
	return statistics ;
    }

    /**
     * Get this server properties.
     */

    public ObservableProperties getProperties() {
	return props ;
    }

    /**
     * Is the underlying file-system case sensitive ?
     * @return A boolean, <strong>true</strong> if file system is case 
     * sensitive, <strong>false</strong> otherwise.
     */
    
    public boolean checkFileSystemSensitivity() {
	return sensitivity;
    }

    /**
     * Get the full URL of Jigsaw's documentation.
     * @return A String encoded URL.
     */

    public String getDocumentationURL() {
	return docurl;
    }

    /**
     * Get the client's debug flags from the properties.
     */

    public final boolean getClientDebug() {
	return client_debug ;
    }
    
    /**
     * Does this server wants clients to try keeping connections alive ?
     */

    public final boolean getClientKeepConnection() {
	return keep ;
    }

    /**
     * Get the request allowed time slice from the properties.
     */

    public final int getRequestTimeOut() {
	return request_time_out ;
    }

    /**
     * Get the connection allowed idle time from the properties.
     */

    public final int getConnectionTimeOut() {
	return connection_time_out ;
    }

    /**
     * Get the client's threads priority from the properties.
     */

    public final int getClientThreadPriority() {
	return client_priority ;
    }

    /**
     * Get the client's buffer size.
     */

    public final int getClientBufferSize() {
	return client_bufsize ;
    }

    /**
     * Get this server host name.
     */

    public String getHost () {
	return host ;
    }

    /**
     * Get this server port number.
     */

    public int getPort () {
	return port ;
    }

    /**
     * Get the server current root resource.
     */

    public HTTPResource getRoot() {
	return root ;
    }

    /**
     * Get the logger for that server.
     * @return A Logger compatible instance, or <strong>null</strong> if 
     * no logger specified.
     */

    public Logger getLogger() {
	return logger;
    }
    
    /**
     * Get the server's edit root resource.
     * The edit root is the one that shows up by default when using the
     * FormResourceEditor resource.
     * @return An HTTPResource.
     * @see w3c.jigsaw.formedit.FormResourceEditor
     */

    public synchronized HTTPResource getEditRoot() {
	if ( editroot == null ) {
	    // Check for the appropriate property:
	    String name = props.getString(EDIT_ROOT_P, null);
	    if ( name != null ) 
		editroot = loadRoot(name);
	    if ( editroot == null )
		editroot = getRoot();
	}
	return editroot;
    }

    /**
     * Get the server URL.
     */

    public URL getURL() {
	if ( url == null ) {
	    try {
		if ( port != 80 ) 
		    url = new URL("http", host, port, "/");
		else
		    url = new URL("http", host, "/");
	    } catch (MalformedURLException ex) {
		throw new RuntimeException("unable to build server's URL");
	    }
	}		
	return url ;
    }

    /**
     * Get the server software string.
     */

    public String getSoftware () {
	return software;
    }

    /**
     * Get the server local port
     */

    public int getLocalPort() {
	return socket.getLocalPort() ;
    }

    /**
     * Get this server identifier.
     */

    public String getIdentifier() {
	return identifier ;
    }

    /**
     * Get the server inet address
     * @return The INET address this server is listening to.
     */

    public InetAddress getInetAddress() {
	return socket.getInetAddress() ;
    }

    /**
     * Get this server root directory.
     */

    public File getRootDirectory() {
	return root_dir ;
    }

    /**
     * Get this server config directory.
     */

    public File getConfigDirectory() {
	File file = props.getFile(CONFIG_P, null);
	return (file == null) ? new File(getRootDirectory(), "config") : file;
    }

    /**
     * Get this server authentication directory.
     */

    public File getAuthDirectory() {
	return new File(getConfigDirectory(), "auth");
    }

    /**
     * Get this server store directory.
     */

    public File getStoreDirectory() {
	return new File(getConfigDirectory(), "stores");
    }

    public File getIndexerDirectory() {
	return new File(getConfigDirectory(), "indexers");
    }

    protected IndexersCatalog indexers = null;
    public IndexersCatalog getIndexersCatalog() {
	if ( indexers == null )
	    indexers = new IndexersCatalog(getDefaultContext());
	return indexers;
    }

    /**
     * Get this server realm catalog.
     */
    
    public RealmsCatalog getRealmsCatalog() {
	return realms ;
    }

    /**
     * Get this server resourcestore manager.
     */

    public ResourceStoreManager getResourceStoreManager() {
	return manager ;
    }

    /**
     * Get the default resource context for that server.
     */

    protected ResourceContext context = null;
    public ResourceContext getDefaultContext() {
	return context;
    }

    /**
     * Cleanup the resources associated with this server context.
     * This method should only be called by the server thread itself, when
     * it is requested to perform the cleanup.
     * @param restart If <strong>true</strong> the server is restarted 
     *     (reinitialized) straight away.
     */

    protected synchronized void cleanup(boolean restart) {
	// Close the accepting socket:
	try {
	    socket.close() ;
	    socket = null ;
	} catch (IOException ex) {
	    errlog ("[cleanup]: IOException while closing server socket.");
	}
	// Shutdow all object that need to be shutdown:
	if ( factory != null )
	    factory.shutdown(true) ;
	factory = null ;
	if ( manager != null )
	    manager.shutdown() ;
	manager = null ;
	if ( shuffler != null )
	    shuffler.shutdown() ;
	shuffler = null ;
	// Unregister to property monitoring
	props.unregisterObserver (this) ;
	errlog ("shutdown completed at: "+new Date()+".") ;
	// Finally close the log
	if ( logger != null )
	    logger.shutdown() ;
	logger = null ;
	// Release any other pointers:
	timer.stopEventManager() ;
	System.out.println (getIdentifier()+": " + getURL() + " done.") ;
	System.out.flush() ;
	// Keep the data neede to reinit (in case needed)
	File init_propfile = props.getFile(PROPS_P, null);
	ObservableProperties init_props = props ;
	String init_identifier = identifier ;
	// Release pointed data:
	identifier = null ;
	manager    = null ;
	factory    = null ;
	shuffler   = null ;
	props      = null ;
	indexer    = null ;
	root       = null ;
	realms     = null ;
	logger     = null ;
	socket     = null ;
	timer      = null ;
	thread     = null ;
	url        = null ;
	restarting = false ;
	finishing  = false ;
	if ( restart ) {
	    try {
		initialize(shm, init_identifier, init_props) ;
	    } catch (Exception ex) {
		// We really can't do more than this here:
		System.out.println("*** server restart failed.") ;
		ex.printStackTrace() ;
	    }
	}
    }

    /**
     * Shutdown the server properly.
     * This methods shutdown the server, and clean-up all its associated 
     * resources. If the current thread is not the server thread, it unblocks
     * the server thread from its accept() call, and forces it to perform
     * the rest of the shutdown operation itself.
     * @see httpd#cleanup
     */

    public synchronized void shutdown () {
	checkpoint();
	errlog ("shutdown inited...(save done)") ;
	finishing = true ;
	try {
	    Socket unlock = new Socket(host, port) ;
	    unlock.close() ;
	} catch (IOException ex) {
	    errlog("[shutdown]: IOException while unblocking server thread.");
	}
    }

    /**
     * Restart the server properly.
     * This methods restarts the server. It cleans-up all its associated 
     * resources, and reinitialize it from scratch. If the current thread is
     * not the server thread, it unblocks
     * the server thread from its accept() call, and forces it to perform
     * the rest of the restart operation itself.
     * @param reload_properties Should we reload the properties from the
     *    property file, or should we just reinitialize from the current set
     *    of properties.
     * @see httpd#cleanup
     */

    public synchronized void restart () {
	errlog ("[restart]: inited !") ;
	finishing    = true ;
	restarting   = true ;
	try {
	    Socket unlock = new Socket(host, port) ;
	    unlock.close() ;
	} catch (IOException ex) {
	    errlog ("[restart]: IOException while unblocking server thread.");
	}
    }

    /**
     * Turn debugging on/off for this instance of httpd server.
     * @param A boolean, true turns debugging on, flase turns it off.
     */

    public void debug (boolean onoff) {
	tracep = onoff ;
    }

    /**
     * Emit a server trace. Traces are used solely for debugging purposes. You
     * should either use <b>log</b> or <b>error</b> to report informations.
     * @param client The client object which wants to report the trace.
     * @param msg The trace message.
     * @see error
     * @see log
     */

    public void trace (Client client, String msg) {
	if ( tracep && (logger != null) )
	    logger.trace (client, msg) ;
    }

    /**
     * Emit a server trace, on behalf of the server itself.
     * @param msg The trace the server wants to emit.
     */
    
    public void trace (String msg) {
	if ( tracep && (logger != null))
	    logger.trace (msg) ;
    }

    /**
     * Emit a log entry.
     * @param client The client whose request is to be logged.
     * @param request The request that has been handled.
     * @param reply The emitted reply.
     * @param nbytes The number of bytes emitted back to the client.
     * @param duration The time it took to process the request.
     */
    
    public void log (Client client
		     , Request request, Reply reply
		     , int nbytes
		     , long duration) {
	if ( logger != null )
	    logger.log (request, reply, nbytes, duration) ;
	statistics.updateStatistics(client, request, reply, nbytes, duration) ;
    }

    /**
     * Emit a log message.
     * @param msg The message to log.
     */

    public void log(String msg) {
	logger.log(msg);
    }

    /**
     * Emit a server error on behalf of some client object.
     * @param client The client.
     * @param msg The error message.
     */

    public void errlog (Client client, String msg) {
	if ( logger != null )
	    logger.errlog(client, msg) ;
    }

    /**
     * Emit an error on behalf of the server.
     * @param msg The error message.
     */

    public void errlog (String msg) {
	if ( logger != null )
	    logger.errlog ("["+identifier+"] "+msg) ;
    }

    /**
     * The prefered form for reporting errors.
     * @param from The object that emited the error.
     * @param msg The error message.
     */
     
    public void errlog(Object from, String msg) {
	if ( logger != null )
	    logger.errlog("["+from.getClass().getName()+"]: "+msg);
    }

    /**
     * Another nice way of reporting errors from an HTTPResource.
     * @param from The resource that trigered the error.
     * @param msg The error message.
     */

    public void errlog(HTTPResource from, String msg) {
	if ( logger != null )
	    logger.errlog(from.getClass().getName()+"@"+from.getURLPath()
			  + ": " + msg);
    }

    /**
     * Emit a fatal error.
     * @param e Any exception that caused the error.
     * @param msg Any additional message.
     */

    public void fatal (Exception e, String msg) {
	System.out.println ("*** Fatal Error, aborting") ;
	System.out.println (this.getClass().getName() + ": " + msg) ;
	e.printStackTrace() ;
	throw new RuntimeException (msg) ;
    }

    public void fatal(String msg) {
	System.out.println("*** Fatal error, aborting") ;
	System.out.println(this.getClass().getName() + ": " + msg) ;
	throw new RuntimeException(msg) ;
    }
	
    /**
     * Emit a warning.
     * Warnings are emited, typically if the configuration is inconsistent,
     * and the server can continue its work.
     * @param msg The warning message.
     */

    public void warning (String msg) {
	System.out.println ("*** Warning : " + msg) ;
    }
	
    /**
     * Emit a warning.
     * @param e Any exception.
     * @param msg Any message.
     */

    public void warning (Exception e, String msg) {
	System.out.println ("*** Warning: " + msg) ;
	e.printStackTrace() ;
    }

    /**
     * Get a shuffler for this server's client.
     * Whenever possible, we use a shuffler program to speed up communication
     * with the client. This methods return whatever the server deems 
     * appropriate for this client shuffler.
     * @return A Shuffler instance, or <strong>null</strong>.
     * @see w3c.jigsaw.core.Shuffler
     */

    public synchronized Shuffler getShuffler (Client client) {
	return shuffler ;
    }

    protected String getBanner() {
	return "Jigsaw["+version+"]";
    }

    public void run () {
	// Emit some traces before starting up:
	System.out.println(getBanner()+": serving at "+getURL());
	System.out.flush() ;
	errlog("started at: "+new Date()+".");
	// Enter the evil loop:
	while ( ( ! finishing) && ( socket != null ) ) {
	    Socket ns = null ;
	    try {
		ns = socket.accept() ;
		ns.setTcpNoDelay(true);
	    } catch (IOException e) {
		e.printStackTrace() ;
		errlog ("failed to accept incoming connection on "+socket) ;
	    }
	    if ( (socket != null) && (ns != null) && (factory != null) ) 
		factory.handleConnection (ns) ;
	}
	// Our socket has been closed, perform associated cleanup.
	cleanup(restarting) ;
    }

    /**
     * Perform the given request on behalf of this server.
     * @param request The request to perform.
     * @return A non-null Reply instance.
     * @exception HTTPException If some error occurs during processing the
     * request.
     */

    public Reply perform(Request request)
	throws HTTPException, ClientException
    {
	// This may be a server-wide request, this is an ugly hack in HTTP spec
	if ( request.getURL() == Request.THE_SERVER ) {
	    if ( request.getMethod().equals("OPTIONS") ) {
		HttpTokenList pub = null;
		synchronized(this) {
		    if ( publicHeader == null )
			pub = HttpFactory.makeStringList(publicMethods);
		    publicHeader = pub;
		}
		Reply reply = request.makeReply(HTTP.OK);
		if ( pub != null )
		    reply.setHeaderValue(Reply.H_PUBLIC, pub);
		return reply;
	    }
	}
	// Create a lookup state, and a lookup result:
	HTTPException error = null;
	LookupState   ls    = new LookupState(request);
	LookupResult  lr    = new LookupResult(root);
	// Run the lookup algorithm of root resource:
	try {
	    if ( root.lookup(ls, lr) ) {
		if (lr.hasReply())
		    return lr.getReply();
	    }
	} catch (HTTPException ex) {
	    error = ex;
	}
	// Let the target resource perform the method
	HTTPResource  target    = lr.getTarget();
	Reply         reply     = null;
	HTTPFilter    filters[] = lr.getFilters();
	int           infilter  = ((filters != null) ? filters.length : 0);
	if ( error == null ) {
	    infilter = 0;
	    // Invoke as much ingoing filters as possible:
	    try {
		if ( filters != null ) {
		    for ( ; infilter < filters.length ; infilter++ ) {
			if ( filters[infilter] == null )
			    continue;
			reply = filters[infilter].ingoingFilter(request
								, filters
								, infilter);
			if ( reply != null )
			    return reply;
		    }
		}
	    } catch (HTTPException ex) {
		error = ex;
	    }
	    // Perform the request:
	    if ((error == null) && (target != null)) {
		try {
		    request.setFilters(filters, infilter);
		    request.setTargetResource(target);
		    reply = target.perform(request);
		} catch (HTTPException ex) {
		    error = ex;
		}
	    } else if ( error == null ) {
		reply = request.makeReply(HTTP.NOT_FOUND);
		reply.setContent("<html><head><title>Not Found</title></head>"
				 + "<body><h1>Invalid URL</h1><p>The URL <b>"
  			         + request.getURL()
				 + "</b> that you requested is not available "
				 +" on that server.</body></html>");
		reply.setContentType(w3c.www.mime.MimeType.TEXT_HTML);
	    }
	}
	// Call the ougoing filters:
	if ((reply == null) || (reply.getStatus() != HTTP.DONE)) {
	    if ( error == null ) {
		for (int i = infilter ; --i >= 0 ; ) {
		    if ( filters[i] == null )
			continue;
		    Reply fr = filters[i].outgoingFilter(request
							 , reply
							 , filters
							 , i);
		    if ( fr != null )
			return fr;
		}
	    } else {
		// Make sure we always invoke appropriate filters:
		for (int i = infilter ; --i >= 0 ; ) {
		    if ( filters[i] == null )
			continue;
		    Reply fr = filters[i].exceptionFilter(request
							  , error
							  , filters
							  , i);
		    if ( fr != null )
			return fr;
		}
		if ((reply = error.getReply()) == null) {
		    reply = request.makeReply(HTTP.INTERNAL_SERVER_ERROR);
		    reply.setContent("<html><head><title>Server Error</title>"
				     + "</head><body><h1>Invalid URL</h1>"
				     + "<p>The URL <b>"
                                     + request.getURL()
				     + "</b>: isn't available "
				     +" on that server.</body></html>");
		    reply.setContentType(w3c.www.mime.MimeType.TEXT_HTML);
		}
	    }
	}
	return reply;
    }

    /**
     * Initialize a new HTTP server.
     * The server wil first be initialized from the available properties,
     * it will than startup, and finally run in its own thread.
     * @param identifier The string identifying this server's occurence.
     * @param props A set of properties to initialize from.
     * @exception IOException If some IO or network operation failed.
     */

    public void initialize (ServerHandlerManager shm
			    , String identifier
			    , ObservableProperties props) 
	throws ServerHandlerInitException
    {
	// Check for an optional upgrade of config files:
	checkUpgrade(shm, identifier, props);
	this.shm = shm;
	// Initialize from properties:
	this.identifier = identifier ;
	this.props = props;
	this.props.registerObserver((PropertyMonitoring) this) ;
	initializeProperties() ;
	// Create the default resource context (shared by all resources):
	this.context = new ResourceContext(this);
	// Create the resource store manager
	initializeResourceStoreManager();
	// Create the resource indexer object
	initializeIndexer();
	// Resurect this server root entity:
	initializeRootResource();
	// Resurect the realms catalog
	initializeRealmsCatalog() ;
	// Initialize the logger object:
	initializeLogger();
	// Initialize property sets
	initializePropertySets();
	// Initialize the shuffler object:
	if ( shuffler_path != null ) {
	    try {
		this.shuffler = new Shuffler (shuffler_path) ;
	    } catch (Error e) {
		warning ("unable to launch shuffler to " 
			 + shuffler_path
			 + ": " + e.getMessage()) ;
		this.shuffler = null ;
	    } catch (Exception e) {
		warning (e, "unable to launch shuffler to " 
			 + shuffler_path
			 + ": " + e.getMessage()) ;
		this.shuffler = null ;
	    }
	}
	if ( this.shuffler != null )
	    trace ("using shuffler at: " + shuffler_path) ;
	// Create this server event manager 
	initializeEventManager();
	// Create the socket, and run the server:
	initializeServerSocket();
	// Yeah, now start:
	this.thread.start();
    }

    public ServerHandler clone(ServerHandlerManager shm
			       , String id
			       , ObservableProperties props) 
	throws ServerHandlerInitException
    {
	// Clone this master server:
	httpd server      = null;
	try {
	    server = (httpd) clone();
	} catch (CloneNotSupportedException ex) {
	    throw new ServerHandlerInitException(this.getClass().getName()
						 + ": clone not supported !");
	}
	server.shm = shm;
	// Nullify some of the cached instance variables:
	server.url = null;
	// Initialize 
	server.identifier = id;
	server.props      = props;
	server.props.registerObserver((PropertyMonitoring) server);
	server.initializeProperties();
	// We basically re-use:
	// - the master indexer
	// - the master realms catalog
	// - the master logger:
	// Initialize our root resource:
	server.initializeRootResource();
	// We use our own event manager
	server.initializeEventManager();
	// We use our own socket, and our own client factory:
	server.initializeServerSocket();
	// Yeah, now start:
	server.thread.start();
	return server;
    }

    private ContainerResource configResource = null;
    public ContainerResource getConfigResource() {
	if ( configResource == null )
	    configResource = new ConfigResource(this);
	return configResource;
    }

    /**
     * Create a new server instance in this process.
     * @param identifier The server's identifier.
     * @param props The server properties.
     */

    public httpd() {
	super();
    }

    public static void usage () {
	PrintStream o = System.out ;

	o.println("usage: httpd [OPTIONS]") ;
	o.println("-id <id>          : server identifier.");
	o.println("-port <number>    : listen on the given port number.");
	o.println("-host <host>      : full name of host running the server.");
	o.println("-root <directory> : root directory of server.") ;
	o.println("-space <directory>: space directory exported by server") ;
	o.println("-p     <propfile> : property file to read.");
	o.println("-trace            : turns debugging on.") ;
	o.println("-config           : config directory to use.") ;
	System.exit (1) ;
    }

    public void checkUpgrade(ServerHandlerManager shm
			     , String identifier
			     , ObservableProperties props) {
	// Check for an upgrade:
	int configvers = props.getInteger(httpd.VERSCOUNT_P, 1);
	int codevers   = httpd.verscount;
	if ( configvers >= httpd.verscount ) 
	    return;
	// Upgrade needed...
	String args[] = shm.getCommandLine();
	if ( args != null ) {
	    for (int i = 0 ; i < args.length ; i++) {
		if ( args[i].equals("-noupgrade") ) {
		    System.out.println("An upgrade is needed, but wasn't run");
		    return;
		}
	    }
	}
	// Perform the upgrade:
	upgrade(configvers, httpd.verscount, args);
    }

    public void upgrade(int from, int to, String args[]) {
	// Upgrade the configuration:
	try {
	    w3c.jigsaw.upgrade.Upgrader upgraders[] = null;
	    upgraders = w3c.jigsaw.upgrade.Upgrade.getUpgrader(from, to);
	    if ( upgraders != null ) {
		for (int i = 0 ; i < upgraders.length ; i++)
		    upgraders[i].upgrade(args);
	    }
	} catch (w3c.jigsaw.upgrade.UpgradeException ex) {
	    System.out.println("upgrade failed:");
	    ex.printStackTrace();
	    System.out.println("+++ Contact www-jigsaw@w3.org for more help");
	}
    }

    public static void main (String args[]) {
	Integer cmdport    = null ;
	String  cmdhost    = null ;
	String  cmdroot    = null ;
	String  cmdspace   = null ;
	String  cmdprop    = null ;
	String  cmdid      = "http-server" ;
	String  cmdconfig  = "config";
	Boolean cmdtrace   = null ;
	boolean noupgrade  = false;

	// 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("-id") && (i+1 < args.length)) {
		cmdid = args[++i];
	    } else if ( args[i].equals ("-host") && (i+1 < args.length)) {
		cmdhost = args[++i] ;
	    } else if ( args[i].equals ("-root") && (i+1 < args.length)) {
		cmdroot = args[++i] ;
	    } else if ( args[i].equals ("-space") && (i+1 < args.length)) {
		cmdspace = 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 ("?") || args[i].equals ("-help") ) {
		usage() ;
	    } else if (args[i].equals("-config") && (i+1 < args.length)) {
		cmdconfig = args[++i];
	    } else if ( args[i].equals("-noupgrade") ) {
		noupgrade = true;
	    } else {
		continue;
		// System.out.println ("unknown option: ["+args[i]+"]") ;
		// System.exit (1) ;
	    }
	}
	// Get the properties for this server:
	ObservableProperties props = null;
	props = new ObservableProperties(System.getProperties()) ;
	// Get the root and configuration directories:
	File root   = ((cmdroot == null)
		       ? new File(props.getProperty("user.dir", null))
		       : new File(cmdroot));
	File config = new File(root, cmdconfig);
	// Locate the property file:
	if (cmdprop == null) {
	    // Try to guess it, cause it is really required:
	    File guess = new File (config, cmdid+".props");
	    if ( ! guess.exists() )
		// A hack for smooth upgrade from 1.0alpha3 to greater:
		guess = new File(config, "httpd.props");
	    cmdprop = guess.getAbsolutePath() ;
	}
	if ( cmdprop != null ) {
	    System.out.println ("loading properties from: " + cmdprop) ;
	    try {
		File propfile = new File(cmdprop) ;
		props.load(new FileInputStream(propfile)) ;
		props.put (PROPS_P, propfile.getAbsolutePath()) ;
	    } 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) ;
	    }
	    System.setProperties (props) ;
	}
	// Check for an upgrade:
	int configvers = props.getInteger(httpd.VERSCOUNT_P, 1);
	if (configvers < httpd.verscount) {
	    System.err.println("+ Jigsaw needs upgrade from internal version "
			       + configvers
			       + " to " + httpd.verscount);
	    if ( noupgrade ) {
		System.err.println("+ Jigsaw cannot run in that version.");
		System.exit(1);
	    }
	    // upgrade(configvers, httpd.verscount, args);
	    return;
	}
	// Override properties with our command line options:
	if ( cmdport != null ) 
	    props.put (PORT_P, cmdport.toString()) ;
	if ( cmdhost != null ) 
	    props.put (HOST_P, cmdhost) ;
	if ( cmdroot != null )
	    props.put (ROOT_P, root.getAbsolutePath()) ;
	if ( cmdconfig != null )
	    props.put(CONFIG_P, config.getAbsolutePath());
	if ( cmdspace != null )
	    props.put (SPACE_P, cmdspace) ;
	if ( cmdtrace != null ) {
	    props.put (TRACE_P, "true") ;
	    props.put (CLIENT_DEBUG_P, "true") ;
	}
	// Install security manager if needed:
	if (Boolean.getBoolean(USE_SM_P)) {
	    SecurityManager sm = new httpdSecurityManager() ;
	    System.setSecurityManager (sm) ;
	}
	// Run the server:
	try {
	    httpd server = new httpd ();
	    server.initialize(null, cmdid, props) ;
	} catch (Exception e) {
	    System.out.println ("*** [httpd]: fatal error, exiting !") ;
	    e.printStackTrace () ;
	}
    }
}
