// ServletWrapper.java
// $Id: ServletWrapper.java,v 1.50 2000/03/03 16:04:07 bmahe Exp $
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html

package org.w3c.jigsaw.servlet;

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

import javax.servlet.*;

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

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

public class ServletWrapper extends FramedResource 
    implements ServletConfig
{

    protected class TimeoutManager implements EventHandler {

	private Object timer     = null;
	private httpd  server    = null;

	/**
	 * Handle timer events. 
	 * @param data The timer closure.
	 * @param time The absolute time at which the event was triggered.
	 * @see org.w3c.tools.timers.EventManager
	 * @see org.w3c.tools.timers.EventHandler
	 */
	public synchronized void handleTimerEvent(Object data, long time) {
	    timer = null;
	    destroyServlet();
	}

	private synchronized void setTimer(long ms) {
	    if ( timer != null ) {
		server.timer.recallTimer(timer) ;
		timer = null ;
	    }
	    timer = server.timer.registerTimer(ms, this, null);
	}

	protected void restart() {
	    start();
	}

	protected void start() {
	    long timeout = getServletTimeout();
	    if (timeout != -1)
		setTimer(getServletTimeout());
	}

	protected synchronized void stop() {
	    if ( timer != null ) {
		server.timer.recallTimer(timer) ;
		timer = null;
	    }
	}
	
	TimeoutManager(httpd server) {
	    this.server = server;
	}

    }

    protected TimeoutManager timeoutManager = null;

    protected int connections = 0;

    protected final static boolean debug = false;

    /**
     * Attributes index - The servlet class name.
     */
    protected static int ATTR_SERVLET_CLASS = -1 ;
    /**
     * Attributes index - The servlet timeout
     */
    protected static int ATTR_SERVLET_TIMEOUT = -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;
    /**
     * Attribute index - Our parent-inherited session context.
     */
    protected static int ATTR_SESSION_CONTEXT = -1;

    static {
	Attribute a   = null ;
	Class     cls = null ;
	try {
	    cls = Class.forName("org.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",
				"org.w3c.jigsaw.servlet.JigsawServletContext",
				null,
				Attribute.DONTSAVE);
	ATTR_SERVLET_CONTEXT = AttributeRegistry.registerAttribute(cls, a);
	// Our session context:
	a = new ObjectAttribute("session-context",
               		     "org.w3c.jigsaw.servlet.JigsawHttpSessionContext",
			     null,
			     Attribute.DONTSAVE);
	ATTR_SESSION_CONTEXT = AttributeRegistry.registerAttribute(cls, a);
	// The servlet timeout:
	a = new LongAttribute("servlet-timeout",
			      null,
			      Attribute.EDITABLE);
	ATTR_SERVLET_TIMEOUT = 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() {
	ResourceReference rr = getParent();
	if (rr != null) {
	    try {
		Resource parent = rr.lock();
		if (parent.definesAttribute("directory"))
		    return (File) parent.getValue("directory", null);
	    } catch(InvalidResourceException ex) {
		ex.printStackTrace();
	    } finally {
		rr.unlock();
	    }
	}
	return null;
    }

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

    public JigsawHttpSessionContext getSessionContext() {
	return (JigsawHttpSessionContext) getValue(ATTR_SESSION_CONTEXT, null);
    }

    protected long getServletTimeout() {
	long timeout = getLong(ATTR_SERVLET_TIMEOUT, 0);
	if (timeout == 0) {
	    JigsawServletContext ctxt = 
		(JigsawServletContext) getServletContext();
	    timeout = ctxt.getServletTimeout();
	}
	return timeout;
    }

    protected void invalidateAllSession() {
	if (debug)
	    System.out.println("Invalidate All Session...");
	JigsawHttpSessionContext ctxt = getSessionContext();
	Enumeration enum = ctxt.getIds();
	while (enum.hasMoreElements())
	    ctxt.getSession((String)enum.nextElement()).invalidate();
    }

    /**
     * Check the servlet class, ans try to initialize it.
     * @exception ClassNotFoundException if servlet class can't be found.
     * @exception ServletException if servlet can't be initialized.
     */
    protected void checkServlet() 
	throws ClassNotFoundException, ServletException
    {
	boolean classmodified =
	    getLocalServletLoader().classChanged(getServletClass());

	if ((! inited) ||
	    classmodified ||
	    (servlet.getClass() != 
	     getLocalServletLoader().loadClass(getServletClass())))
	    {
		inited = launchServlet();
	    }
    }

    protected boolean isInited() {
	return inited;
    }

    protected void service(Request request, Reply reply)
	throws ServletException, IOException
    {
	JigsawHttpServletResponse jRes = null;
	JigsawHttpServletRequest jReq = null;
	if (servlet instanceof SingleThreadModel) {
	    synchronized (this) {
		jRes = new JigsawHttpServletResponse(request, reply);
		jReq = new JigsawHttpServletRequest(servlet, 
						    request, 
						    jRes,
						    getSessionContext());
		jRes.setServletRequest(jReq);
		try {
		    connections++;
		    servlet.service(jReq , jRes);
		} finally {
		    connections--;
		}
		jRes.flushStream(true);
	    }
	} else {
	    jRes = new JigsawHttpServletResponse(request, reply);
	    jReq = new JigsawHttpServletRequest(servlet, 
						request, 
						jRes,
						getSessionContext());
	    jRes.setServletRequest(jReq);
	    try {
		connections++;
		servlet.service(jReq , jRes);
	    } finally {
		connections--;
	    }
	    jRes.flushStream(true);
	}
	timeoutManager.restart();
    }

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

    protected void setValueOfSuperClass(int idx, Object value) {
	super.setValue(idx, value);
    }

    /**
     * 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)) ||
	    (idx == ATTR_PARAMETERS)) {
	    try {
		inited = launchServlet();
	    } catch (Exception ex) {
		String msg = ("unable to set servlet class \""+
			      getServletClass()+
			      "\" : "+
			      ex.getMessage());
		getServer().errlog(msg);
	    }
	} if (idx == ATTR_SERVLET_TIMEOUT) {
	    timeoutManager.restart();
	}
    }

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

    protected synchronized void destroyServlet() {
	if ((servlet != null) && (connections < 1)) {
	    servlet.destroy();
	    servlet = null;
	    inited = false;
	}
    }

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

    public synchronized Servlet getServlet() {
	try {
	    checkServlet();
	} catch (Exception ex) {
	    if (debug)
		ex.printStackTrace();
	}
	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.
     * @exception ServletException if servlet can't be initialized.
     */

    protected boolean launchServlet(Class cls) 
	throws ServletException
    {
	if (debug) {
	    System.out.println("launching Servlet: "+getServletName());
	}
	try {
	    servlet = (Servlet) cls.newInstance();
	    servlet.init((ServletConfig) this);
	    timeoutManager.restart();
	} catch (IllegalAccessException ex) {
	    String msg = ("Illegal access during servlet instantiation, "+
			  ex.getClass().getName()+": "+
			  ex.getMessage());
	    if ( debug )
		ex.printStackTrace();
	    getServer().errlog(this, msg);
	    return false;
	} catch (InstantiationException iex) {
	    String msg = ("unable to instantiate servlet, "+
			  iex.getClass().getName()+": "+
			  iex.getMessage());
	    if ( debug )
		iex.printStackTrace();
	    getServer().errlog(this, msg);
	    return false;
	}
	return (servlet != null) ;
    }

    /**
     * Check if the Servletclass wrapped is a Servlet class without
     * initializing it. (not the same than checkServlet).
     * used by the ServletIndexer.
     * @see org.w3c.jigsaw.servlet.ServletIndexer
     * @return A boolean.
     */
    protected boolean isWrappingAServlet() {
	String clsname = getServletClass();
	if ( clsname == null ) 
	    return false;
	Class c = null;
	try {
	    c = getLocalServletLoader().loadClass(clsname, true);
	    Object o = c.newInstance();
	    return (o instanceof Servlet);
	} catch (Exception ex) {
	    return false;
	}
    }

    /**
     * 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.
     * @exception ClassNotFoundException if servlet class can't be found.
     * @exception ServletException if servlet can't be initialized.
     */

    protected boolean launchServlet() 
	throws ClassNotFoundException, ServletException
    {
	// 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 (getLocalServletLoader().classChanged(clsname)) {
		    createNewLocalServletLoader(true);
		    invalidateAllSession();
		}
		c = getLocalServletLoader().loadClass(clsname, true);
	    } catch (ClassNotFoundException ex) {
		String msg = ("unable to find servlet class \""+
			      getServletClass()+"\"");
		getServer().errlog(msg);
		// re throw the exception
		throw ex;
	    }
	    return (c != null) ? launchServlet(c) : false;
	}
    }

    public boolean acceptUnload() {
	return (servlet == null);
    }

    public void notifyUnload() {
	if (timeoutManager != null)
	    timeoutManager.stop();
	destroyServlet();
    }

    /** 
     * Get or create a suitable LocalServletLoader instance to load 
     * that servlet.
     * @return A LocalServletLoader instance.
     */

    protected synchronized AutoReloadServletLoader getLocalServletLoader() {
	JigsawServletContext ctxt = (JigsawServletContext) getServletContext();
	return ctxt.getLocalServletLoader();
    }

    protected 
	AutoReloadServletLoader createNewLocalServletLoader(boolean keepold) 
    {
	JigsawServletContext ctxt = (JigsawServletContext) getServletContext();
	return ctxt.createNewLocalServletLoader(keepold);
    }

    /**
     * Returns the name of this servlet instance.
     * The name may be provided via server administration, assigned in the 
     * web application deployment descriptor, or for an unregistered (and thus
     * unnamed) servlet instance it will be the servlet's class name.
     * @return the name of the servlet instance
     */
    public String getServletName() {
	return getIdentifier();
    }

    /**
     * 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);
	connections = 0;
	
	if (getServletContext() != null) {
	    timeoutManager = new TimeoutManager((httpd)getServer());
	    timeoutManager.start();
	}

	try {
	    registerFrameIfNone("org.w3c.jigsaw.servlet.ServletWrapperFrame",
				"servlet-wrapper-frame");
	} catch (Exception ex) {
	    ex.printStackTrace();
	}
	
    }

}








