// ServletWrapper.java
// $Id: ServletWrapper.java,v 1.31 1998/06/17 15:01:39 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.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 LocalServletLoader loader  = null;

    protected final static boolean debug = false;

    /**
     * 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;
    /**
     * Attribute index - Our parent-inherited session context.
     */
    protected static int ATTR_SESSION_CONTEXT = -1;
    /**
     * Attribute index - use auto-reload?
     */
    protected static int ATTR_AUTO_RELOAD = -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);
	// Use auto-reload?
	a = new BooleanAttribute("auto-reload",
				Boolean.TRUE,
				Attribute.EDITABLE);
	ATTR_AUTO_RELOAD = AttributeRegistry.registerAttribute(cls, a);
    }
    
    /**
     * Should we use LocalClassLoader? (auto-reload)
     */
    protected boolean getAutoReloadFlag() {
	return getBoolean(ATTR_AUTO_RELOAD, true);
    }

    /**
     * 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 void checkServletClass() {
	if (getAutoReloadFlag()) {
	    if (getLocalServletLoader().classChanged(getServletClass())) {
		inited = launchServlet();
	    }
	} else {
	    if (! inited)
		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);
		servlet.service(jReq , jRes);
		jRes.closeStream();
	    }
	} else {
	    jRes = new JigsawHttpServletResponse(request, reply);
	    jReq = new JigsawHttpServletRequest(servlet, 
						request, 
						jRes,
						getSessionContext());
	    jRes.setServletRequest(jReq);
	    servlet.service(jReq , jRes);
	    jRes.closeStream();
	}
    }

    /**
     * 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))
	    inited = launchServlet();
	if (idx == ATTR_AUTO_RELOAD) {
	    if (getAutoReloadFlag())
		loader = getNewLocalServletLoader(false);
	    else 
		loader = 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 (getAutoReloadFlag()) {
		    if (getLocalServletLoader().classChanged(clsname))
			loader = getNewLocalServletLoader(true);
		    c = getLocalServletLoader().loadServletClass(clsname,
								 true);
		} else {
		    c = Class.forName(clsname);
		}
	    } catch (ClassNotFoundException ex) {
		if ( debug )
		    ex.printStackTrace();
		String msg = ("unable to load servlet class \""+
			      getServletClass()+
			      "\" : "+
			      ex.getMessage());
		getServer().errlog(msg);
	    }
	    return (c != null) ? launchServlet(c) : false;
	}
    }

    public void notifyUnload() {
	destroyServlet();
    }

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

    protected synchronized LocalServletLoader getLocalServletLoader() {
	if ( loader == null ) {
	    loader = new LocalServletLoader(this);
	}
	return loader;
    }

    protected synchronized 
	LocalServletLoader getNewLocalServletLoader(boolean keepold) 
    {
	if ((loader != null) && keepold)
	    loader = new LocalServletLoader(loader);
	else
	    loader = new LocalServletLoader(this);
	return loader;
    }

    /**
     * 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();
	try {
	    registerFrameIfNone("org.w3c.jigsaw.servlet.ServletWrapperFrame",
				"servlet-wrapper-frame");
	} catch (Exception ex) {
	    ex.printStackTrace();
	}
    }

}








