Package org.w3c.jigsaw.http

Source Code of org.w3c.jigsaw.http.httpd

// httpd.java
// $Id: httpd.java,v 1.142 2007/04/10 13:21:01 ylafon Exp $
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html

package org.w3c.jigsaw.http ;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
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 java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.Vector;

import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;

import org.w3c.tools.resources.AbstractContainer;
import org.w3c.tools.resources.DummyResourceReference;
import org.w3c.tools.resources.FilterInterface;
import org.w3c.tools.resources.FramedResource;
import org.w3c.tools.resources.InvalidResourceException;
import org.w3c.tools.resources.LookupResult;
import org.w3c.tools.resources.LookupState;
import org.w3c.tools.resources.ProtocolException;
import org.w3c.tools.resources.ReplyInterface;
import org.w3c.tools.resources.RequestInterface;
import org.w3c.tools.resources.Resource;
import org.w3c.tools.resources.ResourceContext;
import org.w3c.tools.resources.ResourceException;
import org.w3c.tools.resources.ResourceFilter;
import org.w3c.tools.resources.ResourceReference;
import org.w3c.tools.resources.ResourceSpace;
import org.w3c.tools.resources.ServerInterface;

import org.w3c.tools.resources.store.ResourceStoreManager;

import org.w3c.tools.resources.indexer.IndexerModule;
import org.w3c.tools.resources.indexer.IndexersCatalog;
import org.w3c.tools.resources.indexer.ResourceIndexer;

import org.w3c.tools.timers.EventManager;

import org.w3c.jigsaw.auth.RealmsCatalog;

import org.w3c.jigsaw.resources.CheckpointResource;

import org.w3c.jigsaw.daemon.ServerHandler;
import org.w3c.jigsaw.daemon.ServerHandlerInitException;
import org.w3c.jigsaw.daemon.ServerHandlerManager;

import org.w3c.www.http.HTTP;
import org.w3c.www.http.HeaderValue;
import org.w3c.www.http.HttpEntityMessage;
import org.w3c.www.http.HttpFactory;
import org.w3c.www.http.HttpMessage;
import org.w3c.www.http.HttpReplyMessage;
import org.w3c.www.http.HttpRequestMessage;
import org.w3c.www.http.HttpTokenList;

import org.w3c.jigsaw.config.PropertySet;

import org.w3c.util.IO;
import org.w3c.util.ObservableProperties;
import org.w3c.util.PropertyMonitoring;
import org.w3c.util.Status;

import org.w3c.tools.resources.ProtocolException;
import org.w3c.tools.resources.upgrade.Upgrader;

import org.w3c.www.mime.MimeParserFactory;
import org.w3c.www.mime.MimeType;

/**
* <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
      ServerInterface, Runnable, PropertyMonitoring, Cloneable, Status
{
    /**
     * The current displayed version of Jigsaw.
     */
    public static final String version = "2.2.6";
    /**
     * The current internal version counter of Jigsaw.
     * This counter is bumped anytime the configuration needs upgrade.
     */
    public static final int verscount = 4;

    /**
     * debug flag
     */
    public static final boolean debug = true;

    public static final
    String VERSCOUNT_P = "org.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 = "org.w3c.jigsaw.server";
    /**
     * If the Host property is not set (see below), you can select if you
     * want to use FQDN (broken on some jdk implementation) or just the IP
     * address as the default host name, it usually defaults to "false"
     * means, use FQDN.
     */
    public static final String DEFHOSTIP_P = "org.w3c.jigsaw.defhostip" ;
    /**
     * 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            = "org.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 = "org.w3c.jigsaw.root" ;
    /**
     * Name of the property giving the server's config directory.
     */
    public static final String CONFIG_P = "org.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 <org.w3c.jigsaw.root>/WWW.
     */
    public static final String SPACE_P            = "org.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            = "org.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           = "org.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      = "org.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=
                                           "org.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=
                                              "org.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=
                                              "org.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=
                                               "org.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="org.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 = "org.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>org.w3c.jigsaw.core.CommonLogger</code>, which implements the
     * common log format.
     * <p>Property defaults to <code>org.w3c.jigsaw.core.CommonLogger</code>
     */
    public static final String LOGGER_P = "org.w3c.jigsaw.logger" ;
    /**
     * Name of property indicating the "lenient" mode of HTTP parsing.
     * <p>Property defaults to <code>true</code>
     */
    public static final String LENIENT_P = "org.w3c.jigsaw.http.lenient" ;
    /**
     * Name of the property indicating the client factory class.
     */
    public static final String CLIENT_FACTORY_P =
  "org.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="org.w3c.jigsaw.shuffler.path";

    /**
     * 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 = "org.w3c.jigsaw.root.name" ;
    public static final String ROOT_CLASS_P = "org.w3c.jigsaw.root.class" ;

    /**
     * Max number of store loaded in memory.
     */
    public static final String MAX_LOADED_STORE_P="org.w3c.jigsaw.loadedstore";
    public static final int MAX_LOADED_STORE = 128;
    int max_loaded_store = -1;

    /**
     * Max number of store loaded in memory.
     */
    public static final String STORE_SIZE_LIMIT_P="org.w3c.jigsaw.storesize";
    public static final int STORE_SIZE_LIMIT = -1;
    int store_size_limit = -1;
   
    /**
     * 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 = "org.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 = "org.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 = "org.w3c.jigsaw.docurl";

    /**
     * Name of the property indicating the startup classes to load
     */
    public static String STARTUP_P = "org.w3c.jigsaw.startup";

    /**
     * Name of the property indicating the trash directory.
     */
    public static String TRASHDIR_P = "org.w3c.jigsaw.trashdir";

    /**
     * Name of the property indicating the URL of Jigsaw's chekpointer.
     */
    public static
    String CHECKURL_P = "org.w3c.jigsaw.checkpointer";

    /**
     * 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 | TRACE</code>.
     */
    public static
    String PUBLIC_P = "org.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 admin server from JigAdmin.
     */
    public static
    String EDIT_ROOT_P = "org.w3c.jigsaw.edit.root";

    /**
     * Name of the serializer class used to store resources.
     */
    public static String SERIALIZER_CLASS_P = "org.w3c.jigsaw.serializer";

    /**
     * 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 = "org.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 = "org.w3c.jigsaw.unix.group";

    /**
     * Should we show the URL that triggered an error in the error message
     * or not?
     * Displaying it can lead to so-called "cross-scripting" hacks
     */
    public static
    String DISPLAY_URL_ON_ERROR_P = "org.w3c.jigsaw.error.url";
  
    /**
     * The list of currently running servers.
     */
    private static Hashtable servers = new Hashtable() ;

    /* FIXME */ public Thread thread    = null ;

    private String         software  = "Jigsaw/2.2.6";
    private ServerSocket   socket    = null ;
    private Logger         logger    = null ;
    private Shuffler       shuffler  = null ;
    public  EventManager   timer     = null ;
    ClientFactory          factory   = null ;

    // FIXME This is a temporary hack to take care of clones
    protected int[] instances = {1};    // object containing the nb of clones

    /**
     * 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.
     */
    protected 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 ;
    /**
     * Should we display URL on error?
     */
    private boolean uri_error = false;
    /**
     * Are we lenient in HTTP mode?
     */
    private boolean lenient = true;
    /**
     * What client factory class should the server use.
     */
    private String
    factory_class = "org.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 ;
    /**
     * FIXME check
     * The server host name.
     */
    protected String host = null ;
    /**
     * FIXME check
     * The server port.
     */
    protected 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 FramedResource root = null ;
    /**
     * the old root ResourceReference
     */
    private ResourceReference root_reference = null;
    /**
     * FIXME check for clones
     * This server URL.
     */
    protected 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 resource's identifier.
     */
    private String root_name = null ;
    private String root_class = null;
    /**
     * The full URL of Jigsaw's documentation as served by that server.
     */
    private String docurl = null;

    /**
     * The trash directory
     */
    private String trashdir = null;

    /**
     * The full URL of Jigsaw's chekpointer.
     */
    private String checkurl = null;

    /**
     * The list of public methods for that server.
     */
    private  String publicMethods[] = { "GET"
          , "HEAD"
          , "PUT"
          , "POST"
          , "LINK"
          , "UNLINK"
          , "DELETE"
          , "OPTIONS"
          , "TRACE"
    } ;
    /**
     * 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 ResourceReference editroot = null;
    /**
     * the sets of properties of this server
     */
    private Vector         propSet     = new Vector(8);
    /**
     * the catalog of indexers of this server
     */
    private IndexersCatalog indexers = null;
    /**
     * the resource context of this server
     */
    private ResourceContext context = null;
    /**
     * the config resource of this server
     */
    private AbstractContainer configResource = null;
    /**
     * and its dummy resource reference
     */
    private ResourceReference rr_configResource = null;

    // is this server a clone?
    private boolean isAClone = false;
    // our master server ID, if we are a clone
    private String masterID = 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(LENIENT_P) ) {
      lenient = props.getBoolean(name, lenient) ;
      errlog (name + " changed to " + lenient) ;
      return true ;
  } else if ( name.equals(DISPLAY_URL_ON_ERROR_P) ) {
      uri_error = props.getBoolean(name, uri_error);
      errlog (name + " changed to " + uri_error);
      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) ) {
      String tmp_logger_class;
      Logger tmp_logger;
      tmp_logger_class = props.getString(name, logger_class);
      // for now the removal of the logger should be done by hand
      if (!tmp_logger_class.equals(logger_class)) {
    try {
        tmp_logger = (Logger)
      Class.forName(tmp_logger_class).newInstance() ;
    } catch (Exception ex) {
        errlog (name + " change failed (bad logger class)") ;
        return false;
    }
    synchronized (this) {
        if (logger != null) {
      logger.shutdown();
        }
        tmp_logger.initialize(this);
        logger = tmp_logger;
        logger_class = tmp_logger_class;
    }
      }
      return true;
  } 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) ) {
      // we will restart the server
      errlog (name + " switching port : " + props.getInteger(name, 80)) ;
      int newport = props.getInteger(name, 80);
      if (port != newport) {
    int oldport = port;
    port = newport;
    checkpoint();
    ServerSocket newsocket = null;
    try {
        newsocket = factory.createServerSocket();
        socket.close();
        socket = newsocket;
    } catch (Exception ex) {
        try { newsocket.close(); } catch (Exception e) {};
        port = oldport;
        // an error occured, return false
        return false;
    }
      }
      return true ;
  } 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(TRASHDIR_P)) {
      trashdir = props.getString(name, trashdir);
      File dir = new File(trashdir);
      if (! dir.exists())
    dir.mkdirs();
      errlog(name + " changed to "+ trashdir);
      return true;
  } else if ( name.equals(CHECKURL_P) ) {
      checkurl = props.getString(name, checkurl);
      errlog(name + " changed to "+ checkurl);
      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 initializeResourceSpace(String server_name,
           String root_class,
           String root_name,
           String serializer,
           int max_loaded_store)
    {
  Hashtable defs = new Hashtable(11) ;
  defs.put("url", "/");
  defs.put("directory", space_dir) ;
  defs.put("context", getDefaultContext()) ;
  this.manager = new ResourceStoreManager(server_name,
            this.getStoreDirectory(),
            root_class,
            root_name,
            serializer,
            max_loaded_store,
            store_size_limit,
            defs);
    }

    /**
     * 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 ResourceReference loadResource(String name) {
  Hashtable defs = new Hashtable(11) ;
  defs.put("url", "/"+name);
  defs.put("directory", space_dir) ;
  ResourceContext context = new ResourceContext(getDefaultContext());
  defs.put("context", context) ;
  ResourceReference rr = manager.loadRootResource(name, defs);
  if (rr != null)
      context.setResourceReference(rr);
  return rr;
    }

    /**
     * start the automatic checkpoint
     */
    public void startCheckpoint() {
  if (checkurl == null) {
      errlog("checkpointer URL unknown.");
      checkpoint();
      return;
  }
  try {
      LookupState   ls  = new LookupState(checkurl);
      LookupResult  lr  = new LookupResult(root.getResourceReference());
      if (root.lookup(ls,lr)) {
    ResourceReference  target = lr.getTarget();
    if (target != null) {
        try {
      Resource res = target.lock();
      if (res instanceof CheckpointResource) {
          ((CheckpointResource) res).activate();
          errlog("Chekpointer started at: "+new Date()+".");
      } else {
          errlog("The chekpointer url ("+checkurl+
           ") doesn't point to a CheckpointResource");
          checkpoint();
      }
        } catch (InvalidResourceException ex) {
      errlog("Invalid Checkpointer : "+ex.getMessage());
      checkpoint();
        } finally {
      target.unlock();
        }
    } else {
        errlog("can't find Checkpointer");
        checkpoint();
    }
      } else {
    errlog("Checkpointer: lookup fail");
    checkpoint();
      }
  } catch (ProtocolException ex) {
      errlog("Checkpointer : "+ex.getMessage());
      checkpoint();
  }
    }

    /**
     * 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 ResourceReference loadRoot(String name) {
  ResourceReference newroot = null;
  String editRootName = props.getString(EDIT_ROOT_P, null);

  // Restore the appropriate root resource:
  Hashtable defs = new Hashtable(11) ;
  defs.put("url", "/");
  defs.put("directory", space_dir) ;
  ResourceContext context = null;
  if ((editRootName != null) && (! name.equals(editRootName))) {
      if (editroot == null) {
    Hashtable edefs = new Hashtable(11) ;
    edefs.put("url", "/");
    edefs.put("directory", space_dir) ;
    ResourceContext econtext =
        new ResourceContext(getDefaultContext());
    edefs.put("context", econtext) ;
    editroot = manager.loadRootResource(editRootName, edefs);
    if (editroot != null)
        econtext.setResourceReference(editroot);
      }
      context = new ResourceContext(editroot);
  } else {
      context = new ResourceContext(getDefaultContext());
  }
  defs.put("context", context) ;
  ResourceReference rr = manager.loadRootResource(name, defs);
  if (rr != null)
      context.setResourceReference(rr);
  return rr;
    }

    private synchronized FramedResource changeRoot(String name) {
  ResourceReference newroot = loadRoot(name);
  FramedResource oldroot = this.root;
  String oldroot_name = this.root_name;
  if ( newroot != null ) {
      try {
    this.root      = (FramedResource)newroot.lock();
    this.root_name = name;
    if (root_reference != null)
        root_reference.unlock();
    root_reference = newroot;
    return root;
      } catch (InvalidResourceException ex) {
    this.root = oldroot;
    this.root_name = oldroot_name;
    return null;
      }
  }
  return null;
    }

    /**
     * Initialize this server's root resource.
     * @exception ServerHandlerInitException if unable to be initialized.
     */

    private void initializeRootResource()
  throws ServerHandlerInitException
    {
  // Check for un-found root resource:
  if ( changeRoot(root_name) == null ) {
      String err = ("Unable to restore root resource ["+root_name+"]"
        +" from store (not found).");
      throw new ServerHandlerInitException(err);
  }
    }

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

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

    /**
     * Initialize the server logger and the statistics object.
     * @exception ServerHandlerInitException if unable to be initialized.
     */

    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 (getBanner() + ": 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.
     * @exception ServerHandlerInitException if unable to be initialized.
     */

    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) ;
    }

    protected MimeParserFactory getMimeClientFactory(Client client) {
  return new MimeClientFactory(client);
    }

    /**
     * Initialize our event manager.
     */

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

    /**
     * startup classes
     */
    protected void loadStartupClasses() {
  String classes[] = props.getStringArray(STARTUP_P, null);
  if (classes != null) {
      for (int i = 0 ; i < classes.length ; i++) {
    try {
        Class c = Class.forName(classes[i]);
        httpdPreloadInterface hpi =
      (httpdPreloadInterface)c.newInstance();
        hpi.preload(this);
    } catch (ClassNotFoundException cnfex) {
        errlog("Startup class not found : "+cnfex.getMessage());
    } catch (InstantiationException iex) {
        errlog("Unable to instanciate : "+iex.getMessage());
    } catch (ClassCastException ccex) {
        errlog("Startup classes must be instance of "+
         "httpdPreloadInterface: "+ccex.getMessage());
    } catch (IllegalAccessException iaex) {
        errlog("IllegalAccess "+iaex.getMessage());
    }
      }
  }
    }

    /**
     * FIXME protected for now to handle clones
     * Initialize some of the servers instance values from properties.
     * @exception ServerHandlerInitException if unable to be initialized.
     */

    protected void initializeProperties()
  throws ServerHandlerInitException
    {
  // Compute some default values (host and port)
  String defhost  = null ;
  String rootstr  = null ;
  String spacestr = null ;
 
  boolean ip_host = props.getBoolean(DEFHOSTIP_P, false);

  try {
      if (ip_host)
    defhost = InetAddress.getLocalHost().getHostAddress() ;
      else
    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) ;
  uri_error        = props.getBoolean(DISPLAY_URL_ON_ERROR_P, false);
  lenient          = props.getBoolean(LENIENT_P, true);
  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) ;
  root_name        = props.getString(ROOT_NAME_P, "root") ;
  root_class       = props.getString(ROOT_CLASS_P, null);
  max_loaded_store = props.getInteger(MAX_LOADED_STORE_P,
              MAX_LOADED_STORE);
  store_size_limit = props.getInteger(STORE_SIZE_LIMIT_P,
              STORE_SIZE_LIMIT);
  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) ;
  // 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) {
      }
  }
  // Trash Dir
  trashdir = props.getString(TRASHDIR_P, trashdir);
  // checpointer url
  checkurl = props.getString(CHECKURL_P, checkurl);
    }

    /**
     * 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);
    }

    /**
     * Enumerate all the registered property sets
     * @return an enumeration of </code>PropertySet</code>
     */
    public Enumeration enumeratePropertySet() {
  return propSet.elements();
    }

    /**
     * Get a property set matching a specific name
     * @return a Resource, the property set found
     */  
    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 tracsh directory
     */
    public String getTrashDirectory() {
  return trashdir;
    }

    /**
     * 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 FramedResource getRoot() {
  return root ;
    }

    /**
     * get the resource reference of the root resource of the server
     */
    public ResourceReference getRootReference() {
  return root_reference;
    }

    /**
     * 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 JigAdmin
     * It is named "root" in the interface.
     * @return An HTTPResource.
     */

    public synchronized ResourceReference 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 = getRootReference();
      }
  }
  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 space diretory
     */
    public File getSpaceDir() {
  return space_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");
    }

    /**
     * Get this server index directory
     */
    public File getIndexerDirectory() {
  return new File(getConfigDirectory(), "indexers");
    }

    /**
     * Get temp directory
     */
    public File getTempDirectory() {
  return new File(getRootDirectory(), "tmp");
    }

    /**
     * Clean the temp dir.
     */
    protected void cleanTempDirectory() {
  org.w3c.util.IO.clean(getTempDirectory());
    }

    /**
     * get the indexer catalog of this server
     */
    public IndexersCatalog getIndexersCatalog() {
  if ( indexers == null )
      indexers = new IndexersCatalog(
    new ResourceContext(getDefaultContext()));
  return indexers;
    }

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

    /**
     * Get this server resourcestore manager.
     */
    public ResourceStoreManager getResourceStoreManager() {
  return manager ;
    }

    /**
     * Get this server resource space
     */
    public ResourceSpace getResourceSpace() {
  return manager ;
    }

    /**
     * Get the default resource context for that server.
     */
    public ResourceContext getDefaultContext() {
  return context;
    }

    /**
     * Get the lenient value, tru if we are lenient in HTTP parsing
     */
    public boolean isLenient() {
  return lenient;
    }

    /**
     * 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.");
  }
  // FIXME temporary hack for clones
   synchronized( instances ) {
      // remove one instance
      instances[0]--;         // @wplatzer
      if ( factory != null )
    factory.shutdown(true) ;
      factory = null ;
      if ( manager != null && instances[0] == 0) // FIXME (shm)
    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 && instances[0] == 0) // FIXME shm
    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 ;
      // FIXME clones 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 {
        instances[0]++; //FIXME clones
        initialize(shm, init_identifier, init_props) ;
        start();
    } 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.");
  }
  shm.removeServerHandler(this);
  cleanTempDirectory();
    }

    /**
     * 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 httpd#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(Resource from, String msg) {
  if ( logger != null )
      logger.errlog(from.getClass().getName()+"@"+from.unsafeGetURLPath()
        + ": " + 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) ;
    }

    /**
     * Emit a fatal error.
     * @param msg Any error message
     */
    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 org.w3c.jigsaw.http.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) {
    if (debug)
        e.printStackTrace() ;
    errlog ("failed to accept incoming connection on "+socket) ;
    // just in case, as it may have been created.
    try { ns.close(); } catch (Exception ex) {};
    ns = null;
      }
      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 ProtocolException If some error occurs during processing the
     * request.
     * @exception ResourceException If a resource got a fatal error.
     */

    public ReplyInterface perform(RequestInterface req)
  throws ProtocolException, ResourceException
    {
  Request request = (Request) req;
  // 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);
    }
    reply.setContentLength(0);
    return reply;
      }
  }
  if (request.getMethod().equals("TRACE")) {
      // check if the resource can be proxied
      boolean doit = true;
      LookupState   ls = new LookupState(request);
      LookupResult  lr = new LookupResult(root.getResourceReference());
      try {
    if ( root.lookup(ls, lr) ) {
        ResourceReference  target = lr.getTarget();
        if (target != null) {
      try {
          // this is plain ugly and won't work for proxy
          // not based on this resource
          // do we need another way to to this?
          FramedResource fr = (FramedResource) target.lock();
          Class cff = Class.forName(
                  "org.w3c.jigsaw.proxy.ForwardFrame");
          doit = (fr.getFrameReference(cff) == null);
      } catch (Exception ex) {
          // fail miserably to the fallback
      } finally {
          target.unlock();
      }
        }
    }
      } catch (Exception ex) {};
      if (doit) {
    Reply reply = request.makeReply(HTTP.OK);
    reply.setNoCache(); // don't cache this
    reply.setMaxAge(-1);
    // Dump the request as the body
    // Removed unused headers:
    // FIXME should be something else for chuncked stream
    ByteArrayOutputStream ba = new ByteArrayOutputStream();
    try {
        reply.setContentType(new MimeType("message/http"));
        request.dump(ba);
        reply.setContentLength(ba.size());
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    reply.setStream(new ByteArrayInputStream(ba.toByteArray()));
    return reply;
      }
  }
  // Create a lookup state, and a lookup result:

  ProtocolException error = null;
  LookupState   ls = null;
  LookupResult  lr = null;
  // Run the lookup algorithm of root resource:
  // catch exception to get error (FIXME)
  try {
      lr = new LookupResult(root.getResourceReference());
      ls = new LookupState(request);

      if ( root.lookup(ls, lr) ) {
    if (lr.hasReply())
        return lr.getReply();
      }
  } catch (ProtocolException ex) {
      error = ex;
  } catch (Exception ex) {
      /*
       * We have a problem here, the error can be a configuration
       * or resource/extension problem, and it should be a
       * 5xx error, or it is a client side error and it should be
       * a 4xx error, ex, try with "Authorization:" and it fails.
       * For now we will reply with a 400, but with a FIXME
       */
      Reply err = request.makeReply(HTTP.BAD_REQUEST);
      err.setContent("<html><head><title>Bad Request</title></head>\n"
         + "<body><p>The server was not able to "
         + "understand this request</p></body></html>");
      error = new ProtocolException(err);
  }
  // Let the target resource perform the method
  ResourceReference  target = lr.getTarget();
  Reply              reply  = null;

  ResourceFilter filters[]  = lr.getFilters();
  int            infilter   = 0;

  if (error == null) {
      //call the ingoing filters:
      try {
    // temporary target resource !!! WARNING
    request.setTargetResource(target);
    if ( filters != null ) {
        for ( ; infilter < filters.length ; infilter++ ) {
      if ( filters[infilter] == null )
          continue;
      reply = (Reply)filters[infilter].
                                     ingoingFilter(request,
                     filters,
                     infilter);
      if ( reply != null ) {
          return reply;
      }
        }
    }
      } catch (ProtocolException ex) {
    error = ex;
      }
      //perform the request:
      if ((error == null) && (target != null)) {
    request.setFilters(filters, infilter);
    request.setTargetResource(target);
    try {
        FramedResource res = (FramedResource)target.lock();
        reply = (Reply) res.perform(request);
        if (reply == null) {
      reply = request.makeReply(HTTP.NOT_FOUND);
      if (uri_error) {
          reply.setContent("<html><head><title>Not Found" +
               "</title></head>\n"+
               "<body><h1>Invalid" +
               " URL</h1><p>The URL <b>"+
               request.getURL()+
               "</b> that you requested is not" +
               " available "+
               " for this protocol.</body>\n"
               +"</html>");
      } else {
          reply.setContent("<html><head><title>Not Found" +
               "</title></head>\n"+
               "<body><h1>Invalid" +
               " URL</h1><p>The URL" +
               "</b> that you requested is not" +
               " available "+
               " for this protocol.</body>\n"
               +"</html>");
      }
      reply.setContentType(org.w3c.www.mime.MimeType.
               TEXT_HTML);
        }
    } catch (InvalidResourceException ex) {
        //FIXME
        reply = request.makeReply(HTTP.NOT_FOUND);
        if (uri_error) {
      reply.setContent("<html><head><title>Not"+
           " Found</title>"+
           "</head><body><b>The URL <b>"+
           request.getURL()+
           "</b> that you requested is not " +
           "available, "+
           " probably deleted.</body></html>");
        } else {
      reply.setContent("<html><head><title>Not"+
           " Found</title>"+
           "</head><body><b>The URL"+
           " that you requested is not " +
           "available, "+
           " probably deleted.</body></html>");
        }
        reply.setContentType(org.w3c.www.mime.MimeType.TEXT_HTML);
    } finally {
        target.unlock();
    }
      } else {
    reply = request.makeReply(HTTP.NOT_FOUND);
    if (uri_error) {
        reply.setContent("<html><head>\n"+
             "<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>");
    } else {
        reply.setContent("<html><head>\n"+
             "<title>Not Found</title></head>"+
             "<body><h1>Invalid URL</h1><p>The URL"+
             " that you requested is not"+
             " available "+
             " on that server.</body></html>");
    }
    reply.setContentType(org.w3c.www.mime.MimeType.TEXT_HTML);
      }
  }
  // call the outgoing filters:
  if ((reply == null) || (reply.getStatus() != HTTP.DONE)) {
      if ( error == null ) {
    for (int i = infilter ; --i >= 0 ; ) {
        if ( filters[i] == null )
      continue;
        Reply fr = (Reply)filters[i].outgoingFilter(request,
                reply,
                filters,
                i);
        if ( fr != null )
      return fr;
    }
      } else {
    // Make sure we always invoke appropriate filters:
    if (filters != null) {
        for (int i = filters.length ; --i >= 0 ; ) {
      if ( filters[i] == null ) {
          continue;
      }
      Reply fr = (Reply)filters[i].exceptionFilter(request,
                 error,
                 filters,
                 i);
      if ( fr != null ){
          return fr;
      }
        }
    }
    reply = (Reply)error.getReply() ;
    if (reply == null) {
        reply = request.makeReply(HTTP.INTERNAL_SERVER_ERROR);
        if (uri_error) {
      reply.setContent("<html><head>\n"+
           "<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>");
        } else {
      reply.setContent("<html><head>\n"+
           "<title>Server Error</title>"+
           "</head><body><h1>Invalid URL</h1>"+
           "<p>The URL"+
           " isn't available "+
           " on that server.</body></html>");
        }
        reply.setContentType(org.w3c.www.mime.MimeType.TEXT_HTML);
    }
      }
  }
  return reply;
    }

    protected boolean checkUpgrade(String id, ObservableProperties 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 (httpd.verscount == 4) {
          org.w3c.tools.resources.serialization.Serializer serializer =
           new org.w3c.tools.resources.serialization.xml.XMLSerializer();
    props.put(httpd.SERIALIZER_CLASS_P,
        "org.w3c.tools.resources.serialization.xml.XMLSerializer");
    File propsfile    = props.getFile(PROPS_P, null);
    Upgrader upgrader = new Upgrader(id,
             getConfigDirectory(),
             propsfile,
             serializer);
    props.put(httpd.VERSCOUNT_P, String.valueOf(httpd.verscount));
    props.put(httpd.SERVER_SOFTWARE_P, "Jigsaw/"+version);
    try {
        upgrader.upgrade(httpd.verscount);
        return true;
    } catch (Exception ex) {
        System.err.println(ex.getMessage());
        return false;
    }
      }
      return false;
  }
  return true;
    }

    /**
     * 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 ServerHandlerInitException if unable to be initialized.
     */

    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;
  // with an explicit cast for buggy compilers
  this.props.registerObserver((PropertyMonitoring)this) ;
  initializeProperties() ;
  if (! checkUpgrade(identifier, props))
      throw new ServerHandlerInitException("Upgrade failed.");
  // Create the socket, and run the server:
  initializeServerSocket();
    }

    /**
     * start the server
     * it will than startup, and finally run in its own thread.
     */

    public void start ()
  throws ServerHandlerInitException
    {
  if (!isAClone) {
      // Create the default resource context (shared by all resources):
      this.context = new ResourceContext(this);
      //FIXME
      // Create the resource store manager
      initializeResourceSpace(identifier,
            root_class,
            props.getString(EDIT_ROOT_P, root_name),
            props.getString(SERIALIZER_CLASS_P, null),
            max_loaded_store);
      this.context.setSpace(getResourceSpace());
      // Create the resource indexer object
      initializeIndexer();
      // Resurect this server root entity:
      initializeRootResource();
      // Resurect the realms catalog
      initializeRealmsCatalog() ;
      // Initialize property sets
      initializePropertySets();
      // Create this server event manager
      initializeEventManager();
      // Initialize the logger object:
      initializeLogger();
      // 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 (RuntimeException 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) ;
      // startup classes
      loadStartupClasses();
      // Yeah, now start:
      this.thread.start();
  } else {
      httpd mainServ = (httpd) shm.lookupServerHandler(masterID);
      this.context = mainServ.getDefaultContext();
      this.realms = mainServ.realms;
      this.manager = mainServ.manager;
      // We basically re-use:
      // - the master indexer
      // - the master realms catalog
      // FIXED no need to use the same logger! - the master logger:
      // Initialize the logger object:
      initializeLogger();
      // Resurect this server root entity:
      initializeRootResource();
      // We use our own event manager
      initializeEventManager();
      // Yeah, now start:
      this.thread.start();
  }
    }

    /**
     * clone this server
     * @exception ServerHandlerInitException if unable to be initialized.
     */
    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.masterID = server.identifier;
  server.identifier = id;
  server.props      = props;
  server.props.registerObserver((PropertyMonitoring) server);
  server.initializeProperties();
  server.initializeServerSocket();
  server.isAClone = true;
  return server;
    }

    /**
     * get this server config resource
     */
    public ResourceReference getConfigResource() {
  if ( rr_configResource == null ) {
      configResource = new ConfigResource(this);
      rr_configResource = new DummyResourceReference(configResource);
  }
  return rr_configResource;
    }

    /**
     * Give the status of this class as a partial HTML text which will be added
     * into a block level element
     * @return a String, the generated HTML
     */
    public String getHTMLStatus() {
  StringBuffer sb = new StringBuffer();
  if (factory instanceof Status) {
      sb.append(((Status)factory).getHTMLStatus());
  }
  sb.append(manager.getHTMLStatus());
  return sb.toString();
    }

    /**
     * Create a new server instance in this process.
     * @param identifier The server's identifier.
     * @param props The server properties.
     */
    public httpd() {
  super();
    }

    /**
     * this server's usage
     */
    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.") ;
  o.println("-maxstores <int>  : Max number of stores in memory.") ;
  System.exit (1) ;
    }

    /**
     * debugging main
     */
    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;
  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("-id") && (i+1 < args.length)) {
    cmdid = args[++i];
      } else if ( args[i].equals ("-maxstores") && (i+1 < args.length)) {
    maxstores = 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") ;
  }
  if (maxstores != null)
      props.put (MAX_LOADED_STORE_P, maxstores);

  // 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 () ;
  }
    }
}
TOP

Related Classes of org.w3c.jigsaw.http.httpd

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.