/** Author: Matthias Kerkhoff, make@bestoffers.de */
package bestoffers.weblogic.util;

import java.util.Hashtable;
import java.util.Arrays;
import java.util.Comparator;
import java.net.URL;

import com.sun.java.util.collections.List;
import weblogic.common.*;
import weblogic.workspace.common.*;
import weblogic.servlet.internal.ServletContextManager;
import weblogic.servlet.internal.ServletContextImpl;
import weblogic.servlet.internal.ServletStubImpl;
import weblogic.servlet.internal.dd.WebAppDescriptor;
import weblogic.servlet.internal.dd.ServletDescriptor;
import weblogic.t3.srvr.T3Srvr;
import weblogic.t3.srvr.HttpServer;
import java.io.BufferedReader;
import java.io.InputStreamReader;

 /**
	* <pre>This startup class adds support for the load-on-startup tag.
	*
	* It has been tested with WLS 5.1 sp6. It may or may not work with
	* different configurations.
	* (It uses various internal WebLogic classes, so it's highly likely,
	*  that future service packs will require modifications.)
	*
  * To use this class, add the following lines to your weblogic.properties:
	*
	* weblogic.system.startupClass.loadServletsOnStartup=bestoffers.weblogic.util.LoadOnStartup
	*	weblogic.system.startupArgs.loadServletsOnStartup=delay=1500,jspPageName=/dummy.jsp
	*
  * where ...
	*
	* <delay> specifies the time (in milliseconds) that the background thread
	*    should wait until it starts,  and
	*
	* <jspPageName> specifies the (exisiting or non-existing) page, that will be
	*    requested (for each WebApplication), before any servlets are preloaded.
	*
	*	The startupArgs are optional. If they're not present, they will default
	* to delay=1000,jspPage=/this_is_a_workaround_for_the_dreaded_classcast_exception.jsp</PRE>
	*/
public class LoadOnStartup implements T3StartupDef, Runnable {
  
  /**
   * Default constructor. A default constructor is required of any
   * class that implements <tt>weblogic.common.T3StartupDef</tt>.
   */
  public LoadOnStartup() {}
  
  private T3ServicesDef services;
	private String jspPageName;
	private int delay;
  
  /**
   * Sets the services stub. The services stub provides
   * runtime access to WebLogic services.
   */
  public void setServices(T3ServicesDef services) {
    this.services = services;
  }
  
  /**
   * The startup action belongs in this method. The string
   * argument specifies the virtual name of this class by which it
   * is registered in the <tt>weblogic.properties</tt> file,
   * under the <tt>weblogic.system.startupClass</tt> property.
   * The hashtable argument is a set of name-value pairs that
   * are supplied to this method from the 
   * <tt>weblogic.system.startupArgs</tt> property.
   *
   * @param name             Virtual name under which this class is registered
   * @param args             Set of name-value pairs of arguments for this method
   * @exception              java.lang.Exception if there is an error
   */
  public String startup(String name, Hashtable args) throws Exception {
		HttpServer server = T3Srvr.getT3Srvr().httpServer();

		// Temporary disable the server to prevent processing of incoming requests.
		if (server.getEnable()) {
			server.setEnable(false);

			// Default delay for background thread 
			delay = 1000;

			// Could be changed via weblogic.properties
    	try {
				delay = Integer.parseInt( (String)args.get("delay") );
			}
			catch (Exception e) {;}
		
			// This page will be requested to activate the JSP classloader. The page
			// may or may not exist.
			jspPageName = "/this_get_is_a_workaround_for_the_dreaded_classcast_exception.jsp";

			// Could be changed via weblogic.properties
			try {
				String tmp = (String)args.get("jspPageName");
				if (tmp != null && tmp.length() > 0) {
					jspPageName = tmp;
				}
			}
			catch (Exception e) {;}
			
			// Some simple sanity checks
			if ( !jspPageName.startsWith("/") )
				jspPageName = "/" + jspPageName;
				
			if ( !jspPageName.endsWith(".jsp") )
				jspPageName += ".jsp";

			if ( delay < 100 )
				delay = 100;
				 
			/**
			 * Start a background thread - the server will not accept connections
			 * while processing startup classes, so we have to use a background thread.
			 */
			Thread t = new Thread(this);
			t.start();

	    return "Starting load-on-start processing in "  + delay/1000 + " seconds.";
		}
		else
    	return "HTTP server not enabled. No load-on-startup processing.";
  }

  /**
   * This method implements the load-on-startup stuff. It loops through all
   * WebApplication's, retrieves the list of registered servlets and loads
   * them in order of load-on-startup. Servlets without, with an empty or a
	 * load-on-startup value of -1 will not be loaded. (This is not strictly spec
   * conformant. The spec requires that servlets with an empty or an negative
	 * load-on-startup tag should be preloaded.)
 	 * To suppress class cast exceptions, we request for each WebApplication 
	 * a dummy jsp. This activates the JSP classloader, which is the only (known)
	 * work-around for the "dreaded ClassCastException".
	 *
   */
	public void run() {		 
		try {
			// Wait a second to let the server finish it's startup.

			// (If this thread starts too early, the load-on-startup will fail
			//  the sleep amount
			try {	Thread.sleep(delay);	} catch (InterruptedException e) {;}
			
			HttpServer server = T3Srvr.getT3Srvr().httpServer();
			server.setEnable(true);

	    services.log().log("Initalizing load-on-startup-servlets");
	
			ServletContextManager scManager = HttpServer.getServletContextManager();
	
			// Get all registered web-applications,
			ServletContextImpl contexts[] = scManager.getAllContexts();
			WebAppDescriptor waDesc;
			
			// loop through them and...
			for (int i = 0;i < contexts.length; i++) {
				waDesc = contexts[i].getWebAppDescriptor();
				if (waDesc != null) {
	 				services.log().log("Checking WebApplication " + contexts[i].getName() );
	
			    List webAppServlets = waDesc.getServletsList();
					if ( !webAppServlets.isEmpty() ) {
	 					services.log().log("Sorting registered servlets of " + contexts[i].getName() );
	
						// ... sort their servlets by the load sequence number.
						Object[] orderedServlets =  webAppServlets.toArray();
						Arrays.sort( orderedServlets, 
								new Comparator() {
									public int compare(Object left, Object right) {
										return ((ServletDescriptor)left).getLoadSequence() - ((ServletDescriptor)right).getLoadSequence();
									}
								}
							);
		
						// Before we load the servlet(s) we have to send a dummy
						// request to the server. Otherwise we would receive 
						// never-ending ClassCastExceptions.
						String contextPath = contexts[i].getContextPath();
						URL url = new URL("http://localhost:" 
							+ server.serverPort
							+ ((contextPath != null && contextPath != "") ? contextPath : "")
							+ jspPageName);
	 					services.log().log("Trying to do an initial read from " + url);
						
						// Send the request
		        BufferedReader in = null;
						try {	in = new BufferedReader( new InputStreamReader( url.openStream() ));	}	
						catch (Exception e) {;}
						finally {	if (in != null) in.close();	}

						// Finally, we can load the servlets
						ServletDescriptor servletDesc;
						for (int k = 0;k < orderedServlets.length; k++) {
							servletDesc = (ServletDescriptor)orderedServlets[k];
	
							// WLS PROBLEM: WLS gives us no chance to differ between an empty 
							// and a non-existing <load-on-startup> tag. This would be needed
							// for 100% spec conformance.

							// load-on-startup defined ? (-1 is the WLS default value for loadSequence)
							if ( servletDesc.getLoadSequence() != -1) { 
		    				services.log().log("Loading servlet " + servletDesc.getServletName() + " (load-on-startup=" + servletDesc.getLoadSequence()+")" );
							
								ServletStubImpl stub = contexts[i].getStubByName( servletDesc.getServletName() );
								try {
									stub.prepareServlet( );
								}
								catch (Exception e) { services.log().log( e.getMessage() ); }
							}
							else
		    				services.log().log("Skipping servlet " + servletDesc.getServletName() + " (load-on-startup=" + servletDesc.getLoadSequence()+")" );
						}
					}
				}
			}
		}
		catch (Exception e1) {
			try {
				services.log().log( e1.getMessage() ); 
			}
			catch (Exception e2) {;}
		}
  }
}

