// ServletWrapper.java
// $Id: ServletWrapper.java,v 1.14 1997/08/22 11:53:36 bmahe Exp $
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html

package w3c.jigsaw.servlet;

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

import javax.servlet.*;

import w3c.tools.store.*;
import w3c.util.*;
import w3c.www.mime.*;
import w3c.www.http.*;
import w3c.jigsaw.http.*;
import w3c.jigsaw.resources.*;

/**
 * @author Alexandre Rafalovitch <alex@access.com.au>
 * @author Anselm Baird-Smith <abaird@w3.org>
 */

public class ServletWrapper extends FilteredResource implements ServletConfig
{

  private LocalServletLoader loader  = null;

  private final static boolean debug = false;

  public static final 
  String STATE_EXTRA_PATH = "w3c.jigsaw.servlet.extraPath";

  /**
   * Attributes index - The servlet class name.
   */
  protected static int ATTR_SERVLET_CLASS = -1 ;
  /**
   * Attribute index - The init parameters for that servlet.
   */
  protected static int ATTR_PARAMETERS = 1;
  /**
   * Attribute index - Our parent-inherited servlet context.
   */
  protected static int ATTR_SERVLET_CONTEXT = -1;

  static {
    Attribute a   = null ;
    Class     cls = null ;
    try {
      cls = Class.forName("w3c.jigsaw.servlet.ServletWrapper") ;
    } catch (Exception ex) {
      ex.printStackTrace();
      System.exit(0);
    }
    // The servlet class attribute.
    a = new StringAttribute("servlet-class"
			    , null
			    , Attribute.EDITABLE | Attribute.MANDATORY) ;
    ATTR_SERVLET_CLASS = AttributeRegistry.registerAttribute(cls, a) ;
    // This servlet init parameters
    a = new PropertiesAttribute("servlet-parameters"
				, null
				, Attribute.EDITABLE);
    ATTR_PARAMETERS = AttributeRegistry.registerAttribute(cls, a);
    // Our servlet context:
    a = new ObjectAttribute("servlet-context"
			    , "w3c.jigsaw.servlet.ServletDirectory"
			    , null
			    , Attribute.DONTSAVE);
    ATTR_SERVLET_CONTEXT = AttributeRegistry.registerAttribute(cls, a);
  }
    
  /**
   * The servlet wrapped within that Jigsaw resource.
   */
  protected Servlet servlet = null;
  /**
   * Is out servler initialized ?
   */
  protected boolean inited = false;

  /**
   * The Path where we can find the servlet class file.
   */
  public File getServletDirectory() { //FIXME
    ServletDirectory dir = 
      (ServletDirectory) getValue(ATTR_SERVLET_CONTEXT, null);
    if (dir == null)
      return null;
    else return dir.getDirectory();
  }

  /**
   * Servlet stub implementation - Get an init parameter value.
   */

  public synchronized String getInitParameter(String string) {
    ArrayDictionary d = getServletParameters();
    String          v = (d != null) ? (String) d.get(string) : null;
    return v;
  }

  /**
   * Servlet stub implementation - Get all init parameters.
   */

  public synchronized Enumeration getInitParameterNames() {
    // Convert init parameters to hashtable:
    ArrayDictionary d = getServletParameters();
    return (d != null) ? d.keys() : new EmptyEnumeration();
  }

  /**
   * Servlet stub implementation - Get that servlet context.
   */

  public ServletContext getServletContext() {
    return (ServletContext) getValue(ATTR_SERVLET_CONTEXT, null);
  }

  /**
   * Get the class name of the wrapped servlet.
   * @return The class name for the servlet if attribute is defined. Otherwise
   * the class name is deduced from the resource identifier.
   */

  public String getServletClass()
  {
    String sclass =  getString(ATTR_SERVLET_CLASS, null);
    if (sclass == null) {
      String ident = getIdentifier();
      if (ident.endsWith(".class"))
	sclass = ident;
    }
    return sclass;
  }

  /**
   * Get the init parameters for our wrapped servlet.
   * @return An ArrayDictionary instance if the attribute is defined, 
   * <strong>false</strong> otherwise.
   */

  public ArrayDictionary getServletParameters() {
    return (ArrayDictionary) getValue(ATTR_PARAMETERS, null);
  }

  /**
   * Catch assignements to the servlet class name attribute.
   * <p>When a change to that attribute is detected, the servlet is
   * automatically reinitialized.
   */

  public void setValue(int idx, Object value) {
    super.setValue(idx, value);
    if ((idx == ATTR_SERVLET_CLASS) && (value != null))
      inited = launchServlet();
  }

  /**
   * Destroy the servlet we are wrapping.
   */

  protected synchronized void destroyServlet() {
    if (servlet != null) {
      servlet.destroy();
      servlet = null;
    }
  }

  /**
   * Get the servlet we are wrapping.
   * @return A servlet instance, if the servlet is alredy running, 
   * <strong>null</strong> otherwise.
   */

  public Servlet getServlet() {
    return servlet;
  }

  /**
   * Initialize our servlet from the given (loaded) class.
   * @param cls The servlet loaded main class.
   * @return A boolean, <strong>true</strong> if servlet was successfully
   * initialised, <strong>false</strong> otherwise.
   */

  protected boolean launchServlet(Class cls) {
    try {
      servlet = (Servlet) cls.newInstance();
      servlet.init((ServletConfig) this);
    } catch (Exception ex) {
      String msg = ("unable to initialize servlet, exception: "+
		    ex.getMessage());
      if ( debug )
	ex.printStackTrace();
      getServer().errlog(this, msg);
      return false;
    }
    return (servlet != null) ;
  }

  /**
   * Launch the servlet we are wrapping.
   * <p>This method either succeed, or the wrapper resource itself will fail
   * to initialize, acting as transparently as possible (in some sense).
   * @return A boolean, <strong>true</strong> if servlet launched.
   */

  protected boolean launchServlet() {
    // Get and check the servlet class:
    if ( servlet != null )
      destroyServlet();
    String clsname = getServletClass();
    if ( clsname == null ) {
      getServer().errlog(this, "no servlet class attribute defined.");
      return false;
    } else {
      Class c = null;
      try {
	if (loader.classChanged(clsname))
	  loader = new LocalServletLoader(this.loader);
	c = loader.loadClass(clsname, true);
      } catch (ClassNotFoundException ex) {
	if ( debug )
	  ex.printStackTrace();
	String msg = ("unable to load servlet class\""+
		      getServletClass()+
		      "at : "+
		      ex.getMessage());
	getServer().errlog(msg);
      }
      return (c != null) ? launchServlet(c) : false;
    }
  }

  /**
   * Dispatch the give request to our servlet.
   * <p>If the servlet cannot be inititalized, we just throw an error message
   * otherwise, we just delegate that request processing to the underlying 
   * servlet instance.
   * @param request The request to be processed.
   * @exception HTTPException If the wrapped servlet is not initialized.
   */

  public Reply perform(Request request)
    throws HTTPException
  {
    if (loader.classChanged(getServletClass())) {
      inited = launchServlet();
    }

    // Check that the servlet has been initialized properly:
    if ( ! inited ) {
      Reply reply = request.makeReply(HTTP.INTERNAL_SERVER_ERROR);
      reply.setContent("Servlet not configured properly");
      throw new HTTPException(reply);
    } 
    // Dispatch the request:
    Reply reply = createDefaultReply(request, HTTP.OK);
    reply.setContentType(MimeType.TEXT_HTML);
    try {
      servlet.service(new JigsawHttpServletRequest(servlet, request),
		      new JigsawHttpServletResponse(request, reply));
    } catch (Exception ex) {
      if ( debug )
	ex.printStackTrace();
      reply = request.makeReply(HTTP.INTERNAL_SERVER_ERROR);
      reply.setContent("Servlet has thrown exception:" + ex.toString());
    }
    return reply;
  }
    
  /**
   * Jigsaw's lookup on servlets.
   * Once here, we have reached a leaf servlet (or at least the remaining
   * lookup is to be done at the servlet itself). We keep track of the
   * <em>path info</em> and mark that servlet as the target of request.
   * @param ls The lookup state.
   * @param lr The lookup result.
   * @exception HTTPException If some error occurs.
   */

  public boolean lookup(LookupState ls, LookupResult lr)
    throws HTTPException
  {
    // Get the extra path information:
    String extraPath = ls.getRemainingPath(true);
    if ((extraPath == null) || extraPath.equals(""))
      extraPath = "/";
    // Keep this path info into the request, if possible:
    Request request = ls.getRequest();
    if ( request != null )
      request.setState(STATE_EXTRA_PATH, extraPath);
    lr.setTarget(this);
    return super.lookup(ls, lr);
  }

  public void notifyUnload() {
    destroyServlet();
  }

  /**
   * Initialize this servlet wrapper resource.
   * After the wrapper itself is inited, it performs the servlet 
   * initialzation.
   * @param values The default attribute values.
   */

  public void initialize(Object values[]) {
    super.initialize(values);
    loader = new LocalServletLoader(this);
    // Initialize the wrapped servlet (is now the rigt time ?)
    if ( getServletClass() != null )
      inited = launchServlet();
  }

}








