// ServletWrapper.java
// $Id: ServletWrapper.java,v 1.1 2008/09/16 14:12:50 sfwang 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.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;

import java.util.Enumeration;
import java.util.ArrayList;
import java.util.NoSuchElementException;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.SingleThreadModel;
import javax.servlet.UnavailableException;

import org.w3c.tools.timers.EventHandler;
import org.w3c.tools.timers.EventManager;

import org.w3c.util.ArrayDictionary;
import org.w3c.util.EmptyEnumeration;
import org.w3c.util.ThreadCache;

import org.w3c.jigsaw.http.Reply;
import org.w3c.jigsaw.http.Request;
import org.w3c.jigsaw.http.httpd;

import org.w3c.tools.resources.Attribute;
import org.w3c.tools.resources.AttributeHolder;
import org.w3c.tools.resources.AttributeRegistry;
import org.w3c.tools.resources.FramedResource;
import org.w3c.tools.resources.InvalidResourceException;
import org.w3c.tools.resources.IntegerAttribute;
import org.w3c.tools.resources.LongAttribute;
import org.w3c.tools.resources.ObjectAttribute;
import org.w3c.tools.resources.PropertiesAttribute;
import org.w3c.tools.resources.Resource;
import org.w3c.tools.resources.ResourceReference;
import org.w3c.tools.resources.ServerInterface;
import org.w3c.tools.resources.StringAttribute;

import org.w3c.www.http.HTTP;

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

public class ServletWrapper extends FramedResource 
    implements ServletConfig {

    static final ThreadCache threadCache = new ThreadCache("servlet-runner");

    /**
     * The overall thread cache size
     */
    static int global = 0;

    /**
     * The instance amount of the cache size
     */
    private int local = 0;
    
    /**
     * Tunes the thread cache.
     *
     * @param capacity  the required cache capacity of this instance
     */
    private final void tuneCache(int capacity) {
        int update = capacity - local;
        if (update != 0) {
            local = capacity;
            synchronized(threadCache) {
                global = Math.max(global + update, 0);
        	threadCache.setCachesize(global);
                if (debug) {
                    System.out.println("cacheupdate local=" + local + 
                                       " global=" + global);
		}
	    }
	}
    }

    static {
	threadCache.setCachesize(global);
	threadCache.setGrowAsNeeded(true);
	// threadCache.setIdleTimeout(86400000);
	threadCache.initialize();
    }
    
    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 void handleTimerEvent(Object data, long time) {
	    synchronized (this) {
		timer = null;
	    }
            // FIXME, each servlet instance available should have its 
	    // individual timeout manager.
            // Thus, resources could be released as required. This mechanism
	    // would opt for a more fine grained servlet instance management.
	    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(timeout);
	}

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

    }


    // used to pass the runner as a state
    public static final String RUNNER = "org.w3c.jigsaw.servlet.runner";
    // used to signal the end of the servlet in the Reply
    public static final String ENDED = "org.w3c.jigsaw.servlet.ended";

    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 ;
    /**
     * Attributes index - The servlet maxinstances for single thread model
     * servlet instance pool size limitation, tk, 20.10.2001
     */
    protected static int ATTR_SERVLET_INSTANCEMAX = -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 maxinstances:
	a = new IntegerAttribute("servlet-instancemax",
				 null,
				 Attribute.EDITABLE);
	ATTR_SERVLET_INSTANCEMAX = AttributeRegistry.registerAttribute(cls, a);
    }

    /** 
     * Using a limited pool of servlet instances instead of a single 
     * instance, tk, 22.10.2001
     * protected Servlet servlet = null;
     */
    protected final ServletPool servletPool = new ServletPool(); 
    
    /**
     * Is our 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() { 
	ServletContext ctxt = 
	    (ServletContext) getValue(ATTR_SERVLET_CONTEXT, null);
	return ctxt;
    }

    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 int getInstanceMax() { 
        // added for single thread model 
	// servlet instance pool size limitation, tk, 20.10.2001
	int instancemax = getInt(ATTR_SERVLET_INSTANCEMAX, 0);
        
        if (instancemax == 0) {
	    JigsawServletContext ctxt = 
		(JigsawServletContext) getServletContext();
	    instancemax = ctxt.getServletInstanceMax();
	}
        return instancemax;
    }

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

    /**
     * Check the servlet class, and 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
    {
	synchronized(servletPool) { 
	    // tk, 20.10.2001, synchronized loading is necessary at 
	    // least for the non-SingleThreadModel
	    boolean classmodified = 
		getLocalServletLoader().classChanged(getServletClass());
	    
	    if ((!inited) ||
		classmodified ||
		// (servlet.getClass() != 
		//    getLocalServletLoader().loadClass(getServletClass()))
		(servletPool.getLoadedClass() != 
		 getLocalServletLoader().loadClass(getServletClass()))
		) {
		inited = launchServlet();
	    }
	}
    }

    protected boolean isInited() {
	return inited;
    }

    /**
     * Check and eventually load the servlet we are wrapping.
     * This method was added for replacing getServlet() during access checks
     * in order to do perform checks without accessing a servlet instance.
     * @return true if and only if the sevlet has successfully been loaded
     */
    public boolean isServletLoaded() {
	try {
	    checkServlet();
	} catch (Exception ex) {
	    if (debug) {
		ex.printStackTrace();
	    }
	}
	return inited;
    }
    
    /**
     * Helper class for managing a limited pool of servlet instances, 
     * tk, 20.10.2001
     * For the SingleThreadModel instance are created as required and 
     * retained up to a specified limit.
     * For the non-SingleThreadModel one instance only is created 
     * (in accordance with the servlet spec).
     * The first instance initializes the pool with its pars-pro-toto
     * class attributes.
     */
    private class ServletPool implements EventHandler {
        
        // minimum wait time before tuning
        private static final int MINWAIT = 5000;
        
        // maximum delay for freeing instances
        private int idletime = MINWAIT;
        
        // earliest next tuning time (when decreasing)
        private long nexttime = -1L;
        
        // maximum pool size
        private int maximum = 0;
        
        // current pool capacity
        private int capacity = 0;
        
        // current pool usage level
        private int usage = 0;
        
        // list of servlet instances
        private final ArrayList pool = new ArrayList();

        // indicator for SingleThreadModel
        private boolean singleThreaded = false;
        
        // common class of the servlet pool
        private Class loadedClass = null;
        
        // the pool cleanup tuning event
        private Object cleanup = null;
        
        /**
         * Indicates, whether this pool needs to be initialized.
         */
        private final boolean requiresInitialization() {
            return ((!inited)||(capacity < 1)||(loadedClass == null));
        }
        
        /**
         * method for exporting the class common to all loaded servlet 
	 * instances
         * @return common loaded servlet class
         */
        protected Class getLoadedClass() {
            return loadedClass;
        }
        
        /**
         * method for adding a fresh servlet instance to the pool 
	 * (using external synchronization)
         * @param servlet the instance to be added to the pool
         * @return <strong>true</strong> if servlet was successfully added
         */
        protected boolean add(Servlet servlet) {
            if (requiresInitialization()) {
		singleThreaded = (servlet instanceof SingleThreadModel);
		if (singleThreaded) maximum = getInstanceMax(); 
		else maximum = 1;
                if (maximum > 0) {
                    pool.ensureCapacity(maximum);
                }
		loadedClass = servlet.getClass();
            }
            if ((maximum < 1)||(capacity < maximum)) {
		boolean success = pool.add(servlet);
                capacity = pool.size();
                return success;
            }
            else return false;
        }
        
        /**
         * non-blocking method for removing a servlet instance from the
	 * pool (using external synchronization)
         * @return a removed servlet instance or null if the pool is empty
         */
        protected Servlet remove() {
            if ((capacity > 0)&&
                ((usage < 1)||(singleThreaded&&(capacity > usage)))) {
                Servlet servlet = (Servlet)pool.remove(0);
                capacity = pool.size();
                if (capacity < 1) {
                    loadedClass = null;
                }
                return servlet;
            }
            else return null;
        }
        
        /**
         * Uses a final timer for cleaning up the pool.
         *
         * @param wait        wait interval for the next cleaning action (non-positive to stop cleaning)
         */
        protected void clean(long wait) {
            // do something for the end
            EventManager manager = ((httpd)getServer()).timer;
            if (cleanup != null) {
                manager.recallTimer(cleanup);
            }
            if (wait > 0L) {
                cleanup = manager.registerTimer(wait, this, null);
            }
        }
        
        /**
         * Tunes this pool (and also the thread cache).
         * <p>
         * This method implements a simple stragegy, which might
         * be replaced by a better one or just NOP. In other words, 
         * the method has no functional importance apart from 
         * managing resource efficiency.
         *
         * @param increasing    indicates increasing usage
         */
        protected void tune(boolean increasing) {
            int capacity2 = (capacity >> 1);
            if (increasing) {
                if (usage > capacity2) {
                    nexttime = -1L;
                    if (usage >= capacity) {
                        tuneCache(capacity);              
                    }
                }
            }
            else {
                   if (usage < capacity2) {
                       long now = System.currentTimeMillis();
                       if (nexttime < 0) {
                           nexttime = now + idletime;
                           if (debug)
                               System.out.println("considering usage=" + usage + 
                                                   " next=" + capacity2 +
                                                   " delay=" + idletime);
                       }
                       else {
                              long wait = nexttime - now;
                              if (wait <= 0L) {
                                  if (singleThreaded) {
                                      for (int i = capacity; i > capacity2; i--) {
                                           Servlet unused = remove();
                                           if (unused != null) {
                                               ClassLoader loader = switchContext(unused);
                                               try {
                                                     unused.destroy();
                                               }
                                               finally {
                                                         resetContext(loader);
                                               }
                                           }
                                           else break;
                                      }
                                  }
                                  else capacity = capacity2;
                                  nexttime = -1L;
                                  tuneCache(capacity);              
                                  if (debug)
                                      System.out.println("reducing usage=" + usage + 
                                                         (singleThreaded ? " real" : " virtual") +
                                                         " capacity=" + capacity + 
                                                         " maximum=" + maximum + 
                                                         " thread=" + Thread.currentThread().getName());
                              }
                       }
                        
                       if (usage < 1) {
                           // buy an insurance
                           if (debug)
                               System.out.println("finalizing delay=" + idletime);
                           clean((idletime << 1));
                       }
                   }
            }
        }
        
	/**
	 * Handle the cleanup event. 
         *
	 * @param data  The timer closure.
	 * @param time  The absolute time at which the event was triggered.
	 */
	public synchronized void handleTimerEvent(Object data, long time) {
            cleanup = null;
            if (usage < 1) {
               tune(false);
            }
	}
        
        /**
         * blocking method for accessing a servlet instance from the pool
         * @exception ServletException thrown if the pool is not properly
	 * initialized
         */
        protected synchronized Servlet takeServlet() throws ServletException, IOException {
            if (requiresInitialization()) {
                throw new ServletException("Accessing servlet without"+
                        		   " initialization.");
            }
            if (singleThreaded) {
                try {
                      while (true) {
                            if (usage < capacity) {
                                Servlet servlet = (Servlet)pool.get(usage++);
                                tune(true);
                                if (debug)
                                    System.out.println("taking usage=" + usage + 
                                                       " real capacity=" + capacity + 
                                                       " maximum=" + maximum + 
                                                       " servlet=" + servlet.hashCode() + 
                                                       " thread=" + Thread.currentThread().getName());
                                return servlet;
                            } else {
                                if ((maximum < 1)||(capacity < maximum)) {
                                    if (launchServlet(loadedClass)) {
                                        notifyAll();
                                        Thread.currentThread().yield(); 
                                        // give previous waiters a chance
                                    } else {
                                        if (debug)
                                            System.out.println("waiting thread=" + Thread.currentThread().getName());
                                        wait();
                                    }
                                } else {
                                    if (debug)
                                        System.out.println("waiting thread=" + Thread.currentThread().getName());
                                    wait();
                                }
                            }
                      }
                }
		catch (InterruptedException ex) {
                        throw new IOException("Waiting for a free servlet"+
                                              " instance interrupted.");
		}
            } else {
                     // One instance only is used in non single thread case 
                     // (cf. servlet api for details)
                     usage++;
                     capacity = Math.max(capacity, usage);
                     Servlet servlet = (Servlet)pool.get(0);
                     tune(true);
                     if (debug)
                         System.out.println("taking usage=" + usage + 
                                            " virtual capacity=" + capacity + 
                                            " maximum=" + maximum + 
                                            " servlet=" + servlet.hashCode() + 
                                            " thread=" + Thread.currentThread().getName());
                     return servlet;
            }
        }
        
        /**
         * method for releasing a servlet instance into the pool after work
         * @param servlet the instance to be returned to the pool
         * @param duration  the request duration
         * @exception ServletException thrown if pool is not properly 
	 * initialized
         */
        protected synchronized void releaseServlet(Servlet servlet, int duration) 
	    throws ServletException {
            if (requiresInitialization()) {
 		throw new ServletException("Releasing servlet without"+
					   " initialization.");
      	    }
            idletime = Math.max(idletime, duration);
            if (usage > 0) {
		if (singleThreaded) {
		    pool.set(--usage, servlet);
                    tune(false);
                    if (debug)
                        System.out.println("releasing usage=" + usage + 
                                           " real capacity=" + capacity + 
                                           " maximum=" + maximum + 
                                           " servlet=" + servlet.hashCode() +
                                           " thread=" + Thread.currentThread().getName());
		    notifyAll();
		} else {
		    // In this case the servlet instance is shared, i.e. 
		    // we have a counting semaphore only.
		    usage--;
                    tune(false);
                    if (debug)
                        System.out.println("releasing usage=" + usage + 
                                           " virtual capacity=" + capacity +
                                           " maximum=" + maximum + 
                                           " servlet=" + servlet.hashCode() +
                                           " thread=" + Thread.currentThread().getName());
		}
            } else {
		throw new ServletException("Incorrect servlet release"+
					   " occurred.");
	    }
        }
        
        /**
         * method for referencing a servlet instance of the pool
         * This method was added for backward compatibility in order to 
	 * support the deprecated getServlet() method,
         * which is structurally not applicable to the pool mechanism, i.e. 
	 * it always returns null in
         * case of the SingleThreadModel (in accordance with the current 
	 * servlet spec)
         * @return a servlet reference or null
         */
        protected synchronized Servlet getRepresentative() {
            if (requiresInitialization()) {
                return null;
            } else {
                        if (singleThreaded) {
			    // FIXME, here we could also return pool[0],
			    // which is defined but probably taken.
			    // However, using pool[0] in a normal manner might 
			    // cause strange behavior 
			    // due to its single threaded design aspect.
			    return null;
			} else {
			    return (Servlet)pool.get(0);
			}
           }
        }
    }    
    
    protected class ServletRunner implements Runnable, EventHandler {
	Servlet srServlet = null;
	JigsawHttpServletRequest srReq = null;
	JigsawHttpServletResponse srResp = null;
	Thread t = null;
	private Object stimer = null;
	private httpd server = (httpd)getServer();
        
	/**
	 * 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 void handleTimerEvent(Object data, long time) {
	    signalTimeout();
	}

	protected void signalTimeout() {
	    synchronized (this) {
		stimer = null;
	    // }
	    // as the request timeouted, we interrupt the runner's thread
	    // (as this thread may be the cause of the timeout)
	    // synchronized (this) {
		if (t != null) {
		    if (debug)
			System.out.println("Killing " +t);
		    t.interrupt();
		}
	    }
	}

	public void run() {
	    synchronized (this) {
		t = Thread.currentThread();
	    }
            long start = System.currentTimeMillis();                
	    stimer = server.timer.registerTimer(server.getRequestTimeOut(), 
						this, null);
            if (debug) 
                System.out.println("running servlet=" + srServlet.hashCode() +
                                   " thread=" + Thread.currentThread().getName());
                        
	    // synchronization object
	    Object o = null;
	    try {
                
                Reply reply = srResp.getReply();
                if (reply != null) {
                        o = reply.getState(JigsawHttpServletResponse.MONITOR);
                }
                
                ClassLoader loader = switchContext(srServlet);
                try {
        		srServlet.service(srReq , srResp);
                }
                finally {
                            resetContext(loader);
                }
                
		// processing done, release the servlet
                try {
                    int duration = (int)Math.min(System.currentTimeMillis() - start, Integer.MAX_VALUE);
		    servletPool.releaseServlet(srServlet, duration);
		} finally {
		    srServlet = null;
                }
		// and remove the timer
		if ( stimer != null ) {
		    server.timer.recallTimer(stimer);
		    stimer = null;
		}
		srResp.flushStream(true);
	    } catch (UnavailableException uex) {
		String message = null;
		srResp.setStatus(HTTP.SERVICE_UNAVAILABLE);
		if (uex.isPermanent()) {
		    message = "<h2>The servlet is permanently "+
			"unavailable :</h2>"+
			"Details: <b>"+uex.getMessage()+"</b>";
		    try {
			srResp.sendError(HTTP.SERVICE_UNAVAILABLE, message);
		    } catch (IOException ioex) {
			// not much to do now...
		    }
		} else {
		    int delay = uex.getUnavailableSeconds();
		    if (delay > 0) {
			message = "<h2>The servlet is temporarily "+
			    "unavailable :</h2>"+
			    "Delay : "+delay+
			    " seconds<br><br>Details: <b>"+
			    uex.getMessage()+"</b>";
			srResp.getReply().setRetryAfter(delay);
			try {
			    srResp.sendError(HTTP.SERVICE_UNAVAILABLE,message);
			} catch (IOException ioex) {
			    // not much to do now...
			}
		    } else {
			message = "<h2>The servlet is temporarily "+
			    "unavailable :</h2>"+
			    "Details: <b>"+uex.getMessage()+
			    "</b>";
			try {		
			    srResp.sendError(HTTP.SERVICE_UNAVAILABLE,message);
			} catch (IOException ioex) {
			    // not much to do now...
			}
		    }
		}
	    } catch (Exception ex) {
		if (debug) {
		    ex.printStackTrace();
		}
		if (srResp.isStreamObtained()) {
		    try {
			srResp.flushStream(false);
			OutputStream os = srResp.getRawOutputStream();
			if (os != null) {
			    synchronized (os) {
				PrintWriter pw = new PrintWriter(os);
				ex.printStackTrace(pw);
				pw.flush();
			    }
			}
			srResp.flushStream(true);
		    } catch (IOException ioex) {}
		} else {
		    try {
			srResp.sendError(HTTP.INTERNAL_SERVER_ERROR,
					 "Servlet has thrown exception:" + 
					 ex.toString());
		    } catch (IOException ioex) {
			// no stream to write on, fail silently
		    }
		}
	    } finally {
		if ( stimer != null ) {
		    server.timer.recallTimer(stimer);
		    stimer = null;
		}
                if (srServlet != null) {
		    try {
                        int duration = (int)Math.min(System.currentTimeMillis() - start, Integer.MAX_VALUE);
			servletPool.releaseServlet(srServlet, duration);
		    }
		    catch (ServletException ex) {
			// ignore
		    }
                }
		// release the monitor waiting for the end of the reply setup
		if (o != null) {
		    synchronized (o) {
			o.notifyAll();
		    }
		}
		srServlet = null;
		srReq = null;
		// adds the END state
		Reply r = srResp.getReply();
		if (r != null) {
		    r.setState(ENDED, new Object());
		}
		srResp = null;
	    }
	}
	
	ServletRunner(Servlet servlet,
		      JigsawHttpServletRequest request,
		      JigsawHttpServletResponse response) {
	    srServlet = servlet;
	    srReq = request;
	    srResp = response;
 	}
    }

    protected void service(Request request, Reply reply) 
	throws ServletException, IOException {
        
	    JigsawHttpServletResponse jRes = null;
	    JigsawHttpServletRequest jReq = null;
	    /* modified due to servlet pooling, tk, 20.10.2001
	       if (servlet instanceof SingleThreadModel) {
	       synchronized (this) {
	       jRes = new JigsawHttpServletResponse(request, reply);
	       jReq = new JigsawHttpServletRequest(servlet, 
	       request, 
	       jRes,
	       getSessionContext());
	       jRes.setServletRequest(jReq);
	       try {
	       connections++;
	       // FIXME we should reuse a thread rather than
	       // reallocating one for every hit
	       ServletRunner runner = new ServletRunner(servlet, jReq, jRes);
	       reply.setState(RUNNER, runner);
	       runner.start();
	       } finally {
	       connections--;
	       }
	       }
	       } else {
	    */
	    jRes = new JigsawHttpServletResponse(request, reply);
            Servlet servlet = servletPool.takeServlet(); 
            // accessing a fresh or sharable instance, tk, 21.10.2001
	    // jReq = new JigsawHttpServletRequest(servlet,
	    JigsawServletContext jco = 
		                    (JigsawServletContext) getServletContext();
	    jReq = new JigsawHttpServletRequest(servlet, 
						jco,
						request, 
						jRes,
						getSessionContext());
	    jRes.setServletRequest(jReq);
	    // try {
  	    //       connections++;
	    // FIXME we should reuse a thread rather than
	    // reallocating one for every hit
	    // reallocating one for every hit
	    ServletRunner runner = new ServletRunner(servlet, jReq, jRes);
	    reply.setState(RUNNER, runner);
            threadCache.getThread(runner, true);
	    // runner.start();
	    // } finally {
	    //     connections--;
	    // }
	    /* } */
	    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 {
                synchronized(servletPool) { 
                    // synchronization added, tk, 20.10.2001
    		    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 void destroyServlet() {
	// if ((servlet != null) && (connections < 1)) {
	RuntimeException rex = null;
	synchronized(servletPool) {
	    Servlet servlet = servletPool.remove();
	    while (servlet != null) {
	    	try {
                       ClassLoader loader = switchContext(servlet);
                       try {
        			servlet.destroy();
                		// servlet = null;
                       }
                       finally {
                                    resetContext(loader);
                       }
		}
		catch (RuntimeException ex) {
			if (rex != null) {
			    rex = ex;
			}
		}
		finally {
			servlet = servletPool.remove();
		}
	    }
	    inited = (servletPool.getLoadedClass() != null);
            if (!inited) {
                servletPool.clean(0L);
                tuneCache(0);
            }
	}
	if (rex != null) throw rex;
	// }
    }

    /**
     * Get the servlet we are wrapping.
     * @return A servlet instance, if the servlet is alredy running, 
     * <strong>null</strong> otherwise.
     */
    public Servlet getServlet() {
	try {
	    checkServlet();
	} catch (Exception ex) {
	    if (debug)
		ex.printStackTrace();
	}
	return servletPool.getRepresentative();
    }
    
    /**
     * Sets the context classloader for the specified servlet class and
     * returns its predecessor or <code>null</code>.
     *
     * @return  the previous context classloader if any
     */
    private static final ClassLoader switchContext(Servlet servlet) {
        if (servlet != null) {
            ClassLoader newContextClassLoader = servlet.getClass().getClassLoader();
            Thread handler = Thread.currentThread();
            ClassLoader oldContextClassLoader = handler.getContextClassLoader();
            if (newContextClassLoader != oldContextClassLoader) {
                handler.setContextClassLoader(newContextClassLoader);
                return oldContextClassLoader;
            } else return null;
        }
        else return null;
    }
    
    /**
     * Resets the context classloader if applicable.
     *
     * @param loader    the previous context classloader or <code>null</code>
     */
    private static final void resetContext(ClassLoader loader) {
        Thread.currentThread().setContextClassLoader(loader);
    }
    
    /**
     * 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());
	    }
	    Servlet servlet = null;
	    try {
		servlet = (Servlet) cls.newInstance();
                ClassLoader loader = switchContext(servlet);
                try {
                        servlet.init((ServletConfig) this);
                }
                finally {
                            resetContext(loader);
                }
		timeoutManager.restart();
		// modified for servlet pool and executed in a context 
		// synchronized on the servlet pool, tk, 20.10.2001
		return servletPool.add(servlet);
	    } 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;
		throw new ServletException(msg, ex);
	    } catch (InstantiationException iex) {
		String msg = ("unable to instantiate servlet, "+
			      iex.getClass().getName()+": "+
			      iex.getMessage());
		if ( debug ) {
		    iex.printStackTrace();
		}
		getServer().errlog(this, msg);
		// return false;
		throw new ServletException(msg, iex);
	    } catch (ServletException nex) {
		String msg = ("Error while initializing servlet");
		getServer().errlog(this, msg);
		if ( debug ) {
		    nex.printStackTrace();
		}
		getServer().errlog(this, msg);
		// return false;
		throw nex;
	    } catch (Exception oex) {
		String msg = ("Error while loading servlet");
		getServer().errlog(this, msg);
		if ( debug ) {
		    oex.printStackTrace();
		}
		getServer().errlog(this, msg);
		// return false;
                throw new ServletException(msg, oex);
            }
	}

    /**
     * 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();
	    if (inited) { 
		String msg = "relaunching servlet failed due to incomplete \""
		    + getServletClass() + "\" cleanup.";
		getServer().errlog(this, msg); 
		return false;
	    } else {
		// Load appropriate servlet class:
		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);
        return (!inited);
    }
    
    public void notifyUnload() {
	if (timeoutManager != null) {
	    timeoutManager.stop();
	}
	destroyServlet();
    }

    /** 
     * Get or create a suitable LocalServletLoader instance to load 
     * that servlet.
     * @return A LocalServletLoader instance.
     */
    // singleton synchronized already performed at the servlet context.
    // protected synchronized AutoReloadServletLoader getLocalServletLoader() {
    protected 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();
	}
    }
}
