/*  Copyright (c) 2000 by Oracle Corporation. All Rights Reserved. */

package oracle.portal.provider.v1.http;

import java.util.*;
import java.io.*;
import javax.servlet.http.*;
import javax.servlet.*;

import oracle.portal.provider.v1.*;
import oracle.portal.utils.v1.*;
import org.w3c.dom.*;
import oracle.xml.parser.v2.*;
import oracle.webdb.utils.SimpleStringBuffer;

/**
 *  HttpProvider is the entry point into the JPDK runtime.  It is a servlet.
 *  It hides the details of HTTP communication from the provider/portlet
 *  implementor. It is responsible for unmarshalling the HTTP provider request
 *  and mapping that request to the Provider interfaces.  Likewise, it takes
 *  the results of these calls and marshals them into the Http response.<P>
 *
 *  Each HttpProvider instance (servlet) represents a single Provider.  The
 *  particular provider the HttpProvider servlet represents is registered in
 *  one of two ways.  Most commonly, the Provider is registered declaratively,
 *  in the provider descriptor file called provider.xml.  The HttpProvider
 *  servlet locates its descriptor file from a serlvet initArgs.  The initArg
 *  is called <i>provider_root</i>.  Its value defines the provider's root or
 *  home directory.  The HttpProvider locates the provider.xml in this
 *  directory.<p>
 *
 *  <i>Note: when using this method, be sure to configure the HttpProvider
 *  servlet using servlet aliasing.  This allows distinct configurations (and
 *  therefore providers) to run in the same server.</i><p>
 *
 *  Alternatively, one can subclass the HttpProvider servlet to register the
 *  Provider class. In this case, the subclass should implement a single
 *  (overridden) method <code>getProviderClass</code>.  This method returns the
 *  class implementing the provider this servlet represents.<p>
 *
 *  Though the HttpProvider servlet represents a single provider (class), the
 *  servlet maintains distinct provider instances.  One instance is maintained
 *  per portal.  That is if your web provider is registered in two portals, the
 *  HttpProvider servlet will maintain a provider instance for each
 *  portal. Provider instances have the same duration as the HttpProvider
 *  servlet.  That is, once instantiated the instance is maintained (and
 *  reused) by the HttpProvider until it itself is destroyed.<p>
 *
 *  As this servlet is designed to be communicated to via the Portal,
 *  duplicating these requests in a browser to determine if the
 *  serlvet/provider is properly configured and running is difficult.  To
 *  address this the HttpProvider supports a test mode.  If you send a default
 *  (unqualified) request to the HttpProvider it will perform various tests and
 *  output an HTML response indicating status.  For example, in Apache, if the
 *  HttpProvider is configured with the servlet alias "sample" you commonly
 *  would access it via the URL "http://myserver/servlet/sample.  Invoking the
 *  HttpProvider in this manner, without any parameters, causes it to run
 *  configuration test and report the results back to you in your browser.<p>
 *
 *  HttpProvider is responsible for initializing the provider instance. This
 *  can occur by one of two ways. The first is if the provider implements
 *  {@link oracle.portal.provider.v1.Provider}. In this case HttpProvider calls
 *  the {@link oracle.portal.provider.v1.Provider#init(long,ProviderLog)}
 *  method. The second is if the provider additionally implements the {@link
 *  oracle.portal.utils.v1.Initializable} interface. In this case, the {@link
 *  oracle.portal.utils.v1.Initializable#init(ParameterMap)} method is called.
 *  The {@link oracle.portal.utils.v1.ParameterMap} passed contains the same
 *  information as the first method, accessed using the parameter names <p>
 *
 *  <code>ProviderConstants.PROVIDER_ID</code><br>
 *  <code>ProviderConstants.PROVIDER_LOG</code><p>
 *  
 *  and additional context information, such as init args, which are also
 *  accessible using the {@link
 *  oracle.portal.utils.v1.ParameterMap#getParameterValue(String)} method.
 */

public class HttpProvider extends HttpServlet implements ProviderLog {

    /**
     *  The constant that is used as the key for retrieving the
     *  ServletConfig from a ServletPortletRenderRequest using getAttribute().
     */
    public static final String SERVLET_CONFIG = "oracle.portal.ServletConfig";

    /**
     *  The constant that is used as the key for retrieving the
     *  ServletContext from a ServletPortletRenderRequest using getAttribute().
     */
    public static final String SERVLET_CONTEXT = "oracle.portal.ServletContext";

    /**
     *  The constant that is used as the key for retrieving the
     *  HttpServletRequest from a ServletPortletRenderRequest using getAttribute().
     */
    public static final String SERVLET_REQUEST = "oracle.portal.HttpServletRequest";

    /**
     *  The constant that is used as the key for retrieving the
     *  HttpServletResponse from a ServletPortletRenderRequest using getAttribute().
     */
    public static final String SERVLET_RESPONSE = "oracle.portal.HttpServletResponse";

    /**
     *  The constant that is used as the key for retrieving the
     *  PortletRenderRequest from a HttpServletRequest using getAttribute().
     *  This is provided so renderers that are implemented as servlets or jsps have
     *  a way to get back to the RenderRequest if they need it.
     */
    public static final String PORTLET_RENDER_REQUEST = "oracle.portal.PortletRenderRequest";

   /**
     *  The constant that is used as the key for retrieving the
     *  ProviderUser from the servlet session.
     *  This is provided as an alternative way to reference the current user.
     */
    public static final String PROVIDER_USER = "oracle.portal.provider.user";

    /**
     *  The constant that defines the expires cache header label.
     *  Oracle Portal supports expires-based caching.  To use this facility, a portlet
     *  must write a header value indicating the number of minutes this response is
     *  valid for.
     */
    public static final String EXPIRES_HEADER = "X-ORACLE-EXPIRES";

    /**
     *  The constant that defines the the validate cache header label.
     *  Oracle Portal supports validation-based caching.  I.e. the portal asks the portlet
     *  if the current (cached) version is still valid. If it is then it is resused
     *  rather then recomputed.  To support this facility, the portlet must tag their
     *  response with a "version".  This is written as a header field.  This constant
     *  defines this header field's name.  Subsequent requests from the Portal contain
     *  this version header.  Clients are expected to read this header and check whether
     *  the cached version is still valid.
     */
    public static final String CACHE_VERSION_HEADER ="ETag";

    /**
     *  The constant that defines the validate cache header label that identifies
     *  the cache (user) level being specified.
     *  Oracle Portal supports validation-based caching.  I.e. the portal asks the portlet
     *  if the current (cached) version is still valid. If it is then it is resused
     *  rather then recomputed.  To support this facility, the portlet must tag their
     *  response with a "version".  As a portlet can render either a user specific
     *  version as well as a generic system (default) version, the portlet must also
     *  relate which response is being refrenced. This is written as a header field.
     *  This constant defines this header field's name.  Values are expected to be
     *  either "USER" or "SYSTEM" though it is better to use the constants defined
     *  herein.
     */
    public static final String CACHE_LEVEL_HEADER = "Cache-Control";

    /**
     *  The constant that identifies "user" level caching.
     *  Oracle Portal supports validation-based caching.  I.e. the portal asks portlet
     *  if the current (cached) version is still valid. If it is then it is resused
     *  rather then recomputed.  To support this facility, the portlet must tag their
     *  response with a "version".  As a portlet can render either a user specific
     *  version as weel as a generic system (default) version, the portlet must also
     *  relate which response is being refrenced. This is written as a header field.
     *  This constant is the value used for this header field that indicates the
     *  response is user specific.
     */
    public static final String CACHE_LEVEL_USER = "USER";

    /**
     *  Constant integer value used to indicate user level caching.
     */
    public static final int USER_LEVEL = 1;

    /**
     *  The constant that identifies "system" level caching.
     *  Oracle Portal supports validation-based caching.  I.e. the portal asks portlet
     *  if the current (cached) version is still valid. If it is then it is resused
     *  rather then recomputed.  To support this facility, the portlet must tag their
     *  response with a "version".  As a portlet can render either a user specific
     *  version as weel as a generic system (default) version, the portlet must also
     *  relate which response is being refrenced. This is written as a header field.
     *  This constant is the value used for this header field that indicates the
     *  response is system (default).
     */
    public static final String CACHE_LEVEL_SYSTEM = "SYSTEM";

    /**
     *  Constant integer value used to indicate system level caching.
     */
    public static final int SYSTEM_LEVEL = 0;

    /**
     *  The constant for the validation based cache header label that is used to
     *  indicate the existing (cached) version is valid and should be reused.
     *  Oracle Portal supports validation-based caching.  I.e. the portal asks portlet
     *  if the current (cached) version is still valid. If it is then it is resused
     *  rather then recomputed. The indication of whether to update the cache or
     *  reuse the response in the cache is written as a header field.
     *  This constant defines this header field's name.  Use the constant
     *  CACHE_UPDATE_HEADER_VALUE as this header's value to indicate the current
     *  response should be cached (overwritting the existing cached response).
     *  Use CACHE_USE_HEADER_VALUE as this header's value to indicate the
     *  Portal should use the response in its cache.
     */
    public static final String CACHE_USE_HEADER = "X-ORACLE-CACHE";

    /**
     *  The constant value that indicates the current response is new and should
     *  replace the existing cached content.
     *  Oracle Portal supports validation-based caching.  I.e. the portal asks portlet
     *  if the current (cached) version is still valid. If it is then it is resused
     *  rather then recomputed. The indication of whether to update the cache or
     *  reuse the response in the cache is written as a header field.
     *  This constant defines the value for CACHE_USE_HEADER that indicates the current
     *  response should be cached (overwritting the existing cached response).
     */
    public static final String CACHE_UPDATE_HEADER_VALUE = "WRITE";

    /**
     *  The constant value that indicates the current cached content is still valid
     *  and should be returned to the requester.
     *  Oracle Portal supports validation-based caching.  I.e. the portal asks portlet
     *  if the current (cached) version is still valid. If it is then it is resused
     *  rather then recomputed. The indication of whether to update the cache or
     *  reuse the response in the cache is written as a header field.
     *  This constant defines the value for CACHE_USE_HEADER that indicates the cached
     *  response is still valid and should be used.
     */
    public static final String CACHE_USE_HEADER_VALUE = "READ";

    /**
     *  The header used for oracle specific request id.
     */
    public static final String REQUEST_ID_HEADER = "Oracle-ECID";

    // initArg parameter providers root directory (location of provider.xml)
    public static final String PROVIDER_ROOT = "provider_root";

    // initArg parameter for provider session timeout (default 1800000ms)
    public static final String SESSION_TIMEOUT = "sessiontimeout";

    // initArg parameter to switch on auto-reload (Default is FALSE)
    public static final String AUTO_RELOAD = "auto_reload";

    // initArg parameter to switch on debug level (Default is -1)
    // < 0: off, 0: performance logging, 1: debug level 1, 2: debug level 2
    public static final String DEBUG_LEVEL = "debuglevel";

    // initArg parameter to switch on test page (Default is -1)
    // <= 0: Off, 1: Show page w/o stack trace, 2: show page w/stack trace
    public static final String SHOW_TEST_PAGE = "show_test_page";

    // initArg parameter to control (Default is 300000ms - 5mins)
    // <= 0: Off, 1: Show page w/o stack trace, 2: show page w/stack trace
    public static final String RELOAD_INTERVAL = "reload_interval";

    private static String PROVIDER = "provider";

    private Class mProviderClass = null;
    private Provider[] mProviders = null;
    private int mProviderCount = 0;
    private boolean mAutoReload = false;
    private int mReloadInterval = 300000;
    private int mDebugLevel = -1;
    private int mShowTestPage = -1;
    private Object mLock = new Object();

    // The thread monitor for this provider
    private ProviderThreadMonitor mProviderThreadMonitor;

    /**
     *  Standard servlet init method.  Override to get custom initArgs or to do
     *  general initialization.  However, you must call super() for HttpProvider
     *  to work correctly as this is where getProviderClass() is called.
     */
    public void init(ServletConfig sc) throws ServletException 
    {
        super.init(sc);
        mProviders = new Provider[5];

        SimpleStringBuffer buf = new SimpleStringBuffer(100);
        buf.append("CONFIG: provider_root=");
        buf.append(sc.getInitParameter(PROVIDER_ROOT));
        log(buf.toString());

        buf.setLength(0);
        buf.append("CONFIG: sessiontimeout=");
        buf.append(sc.getInitParameter(SESSION_TIMEOUT));
        log(buf.toString());

        String paramStr = sc.getInitParameter(AUTO_RELOAD);
        if (paramStr != null)
        {
            mAutoReload = paramStr.equalsIgnoreCase("true");
            buf.setLength(0);
            buf.append("CONFIG: auto_reload=");
            buf.append(mAutoReload);
            log(buf.toString());
        }
        
        paramStr = sc.getInitParameter(RELOAD_INTERVAL);
        if (paramStr != null)
        {
            try
            {
                mReloadInterval = Integer.parseInt(paramStr); 
            }
            catch (NumberFormatException nfe)
            {
                buf.setLength(0);
                buf.append("CONFIG: NumberFormatException reading ");
                buf.append("reload_interval - using default setting");
                log(buf.toString());
                mReloadInterval = 300000;
            }
            buf.setLength(0);
            buf.append("CONFIG: reload_interval=");
            buf.append(mReloadInterval);
            log(buf.toString());
        }

        // get the debug level
        paramStr = sc.getInitParameter(DEBUG_LEVEL);
        if (paramStr != null)
        {
            try
            {
                mDebugLevel = Integer.parseInt(paramStr);
            }
            catch (NumberFormatException nfe)
            {
                buf.setLength(0);
                buf.append("CONFIG: NumberFormatException reading ");
                buf.append("debuglevel - using default setting");
                log(buf.toString());
            }
            buf.setLength(0);
            buf.append("CONFIG: debuglevel=");
            buf.append(mDebugLevel);
            log(buf.toString());
        }

        // get the test page setting
        paramStr = sc.getInitParameter(SHOW_TEST_PAGE);
        if (paramStr != null)
        {
            try
            {
                mShowTestPage = Integer.parseInt(paramStr);
            }
            catch (NumberFormatException nfe)
            {
                buf.setLength(0);
                buf.append("CONFIG: NumberFormatException reading ");
                buf.append("show_test_page - using default setting");
                log(buf.toString());
                // Use default if invalid
            }
            buf.setLength(0);
            buf.append("CONFIG: show_test_page=");
            buf.append(mShowTestPage);
            log(buf.toString());
        }

        // Create a provider monitor in a new thread and start it.
        mProviderThreadMonitor = new ProviderThreadMonitor(this, mDebugLevel);
        Thread t = new Thread(mProviderThreadMonitor);
        t.start();
    }

    /**
     *  Standard servlet destroy method.  Overridden to stop the provider
     *  thread monitor.
     */    
    public void destroy()
    {
        mProviderThreadMonitor.stopMonitor();
        super.destroy();
    }


    /**
     *  This will display the provider test page.
     *  @param   res the HttpServletResponse object
     *  @param   provider a Provider object with a ProviderID of -1
     *           This is null if an error was encountered in instantiating
     *           or initialising it
     *  @param   errText a string reporting the error obtained in instantiating
     *           or initialising the provider, null if no error was encountered
     *           in this case, the provider parameter must be non-null
     *
     */
    private void showTestPage(HttpServletResponse res,
                              Provider provider, 
                              String stackTrace,
                              String  errText)
        throws IOException
    {
        if (mDebugLevel <= 0 && mShowTestPage <= 0)
        {
            // both debug and showTestPage are turned off so deny access to the
            // test page
            res.sendError(HttpServletResponse.SC_FORBIDDEN);
            return;
        }
        res.setContentType("text/html;charset=UTF-8");
        PrintWriter out;
        try
        {
            out = res.getWriter();
        }
        catch (Throwable t)
        {
            log("IO problems: ");
            log(t.getMessage());
            return;
        }
        out.println("<html><head><title>JPDK Test Page</title><body>");
        if(provider == null)
        {
            out.println("<h3> Error! </h3><h4>");
            out.println(errText);
            out.println("</h4>");
            if (mDebugLevel > 1 || mShowTestPage > 1)
            {
                // either debug or showTestPage is set to a high enough level
                // to enable the dumping of the stacktrace.
                out.println(stackTrace);
            }
        }
        else
        {
          out.println("<h3>Congratulations!  You have successfully reached your Provider's Test Page. </h3>");
          out.println("Checking for components:<br>");

          try
          {
            Portlet[] p = provider.getPortlets(0,99,false,null);
            if(p != null)
            {
              out.println("Portlets are:<br>");
              out.println("<blockquote>");
              for(int i=0; i<p.length; i++)
              {
                  out.println((p[i]).getName(p[i].getDefaultLocale()));
                  out.println("<br>");
              }
              out.print("</blockquote>");
            }

          }
          catch(ProviderException pe)
          {
            out.println("<i> couldn't get portlet names</i>");
          }
          out.print("<br>Oracle JSP: ");
          try
          {
            Servlet s = this.getServletContext().getServlet("oracle.jsp.JspServlet");
            out.println("<i>detected</i><br>");
          }
          catch (Throwable t)
          {
            out.println("<i>failed .. check log for more information.</i>");
            this.getServletContext().log("Problems detecting Oracle JSP. ");
            this.getServletContext().log(t.getMessage());
          }
          out.println("<br>Recognizing initArgs: Usual initArgs are <i>provider_root</i> and <i>sessiontimeout</i>.<br>");
          out.println("<blockquote>");
          Enumeration e = this.getServletConfig().getInitParameterNames();
          String s;
          String v;
          while (e.hasMoreElements())
          {
            s = (String) e.nextElement();
            v = this.getServletConfig().getInitParameter(s);
            out.println(s);
            out.println(": ");
            out.println(v);
            out.println("<br>");
          }
          out.println("</blockquote>");
        }
        out.println("</body></head></html>");
    }

    /**
     *  Standard servlet service method.  All of the function to adapt from the
     *  http request to the Provider interfaces is implemented here.
     *  In general you should not override this method.
     *  Added support for a test request so developers can determine if they have
     *  properly installed/configured their provider.  A test request is a browser
     *  request to the Provider Adpater (servlet) with no parameters.
     *  If a test page is requested, a provider with dummy id = -1 is
     *  instantiated and initialised. If successful, showTestPage is invoked
     *  with the provider object as parameter, if unsuccessful, the error string
     *  is passed to showTestPage.
     *  Also logs a warning if a request takes more than a given time to be
     *  processed. This time is specified in milliseconds.
     *  @param request HttpServletRequest : detects if this is a request for a test page
     *  @param response HttpServletResponse :
     */
    public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        // Start up a performance logger object
        PerformanceLogger pLog = new PerformanceLogger();

        // Start the request timer
        pLog.startRequest();

        // The start time to be used by the portlet execution tracking code
        long startTime = System.currentTimeMillis();

        ServletProviderRequest pRequest;
        ServletProviderResponse pResponse;
        Provider provider = null;
        try
        {
            pRequest = new ServletProviderRequest(this.getServletConfig(),
                                                new Servlet20RequestWrapper(request),
                                                response,
                                                startTime,
                                                pLog);
            pResponse = new ServletProviderResponse(pRequest);
        }
        catch (Exception e)
        {
            log(translateException(e));
            throw new ServletException(e.toString());
        }
        try
        {
            provider = getProvider(pRequest, pResponse);
            if (pRequest.isTest())
            {
                showTestPage(response, provider, null, null);
            }
            else
            {
                int action = pRequest.getAction();
                dispatchProviderAction(action, provider, pResponse, pLog, pRequest);
                long maxTime = pRequest.getPortletTimeout();
                // Check the execution time and log a warning if necessary.
                if (action == HttpProviderConstants.ACTION_SHOW_PORTLET)
                {
                    long timeTaken = System.currentTimeMillis() - startTime;
                    if (timeTaken > maxTime)
                    { 
                        SimpleStringBuffer xsTimeMsg = new SimpleStringBuffer(150);
                        xsTimeMsg.append("WARNING: Portlet (");
                        xsTimeMsg.append(pRequest.getProviderId());
                        xsTimeMsg.append(".");
                        xsTimeMsg.append(pRequest.getPortletId());
                        xsTimeMsg.append(".");
                        xsTimeMsg.append(pRequest.getPortletName());
                        xsTimeMsg.append(" user:");
                        xsTimeMsg.append(pRequest.getUser());
                        xsTimeMsg.append(") exceeded the max time allowed of ");
                        xsTimeMsg.append(maxTime);
                        xsTimeMsg.append("ms. Actual time taken was ");
                        xsTimeMsg.append(timeTaken);
                        log(xsTimeMsg.toString());
                    }
                } 
            }             
        }
        catch (Exception e)
        {
            // log the exception and swallow it. All exceptions will be reported
            // back to the portal using the request response (either setting the 
            // response status and error message or sending a SOAP fault.
            String stackTrace = translateException(e);
            String errText = e.toString();
            log(stackTrace);
            if (pRequest.isTest())
            {
                showTestPage(response, null, stackTrace, errText);
            }
        }

        // Finish the logging request time at this stage. Log the stats if
        // debugLevel is 0 or above.
        pLog.finishRequest();

        if (mDebugLevel >= 0)
        {
            pLog.writeLog(this);
        }

        // Append to portlet content if the incoming url contains the debug
        // parameter.
        if(pRequest.isHtmlDebugOn())
        {
            pLog.writeResponse(response);
        }
    }

    /**
     *  Returns the Provider class this servlet manages.  By default this method
     *  looks for the class name in two places.  First it looks for an initArg named
     *  <code>provider</code> whose value is a fully qualified
     *  class name.  If that doesn't exist then it checks to see if the initArg
     *  provider_root is set.  If it is it looks for a provider.xml file in this
     *  root directory and tries to read the classname fron the provider tag. (Its
     *  an attribute named "class").<p>
     *  Developers can override this method returning their Provider
     *  if they want to have a distinguished servlet on the server.
     */
    public Class getProviderClass() throws ServletException
    {
      String providerClassName = getInitParameter(PROVIDER);
      if (providerClassName == null)
      {
        try
        {
          providerClassName = DefaultProvider.getProviderClassFromXml(this);
        }
        catch(ProviderException pe)
        {
          throw new ServletException("Failed to parse provider.xml: " + pe.getMessage());
        }
      }
      if (providerClassName == null) throw new ServletException("No Provider class defined via this servlet's initArgs or provider.xml");
      try
      {
        return Class.forName(providerClassName);
      }
      catch (ClassNotFoundException e)
      {
        throw new ServletException("Unable to find Provider class " + providerClassName);
      }
    }

    /**
     *  Log the message to the Servlet log.
     *  The HttpProvider servlet implements the logging function for the provider runtime.
     *  This method is one of two defined in the ProviderLog interface.  It logs the
     *  provided message to the servlet log.
     *
     *  @param msg  the message to log.
     */
    public void log(String msg) {
        this.getServletContext().log(msg);
    }

    /**
     *  Log the exception to the Servlet log.
     *  The HttpProvider servlet implements the logging function for the provider runtime.
     *  This method is one of two defined in the ProviderLog interface.  It logs the
     *  provided message to the servlet log along with information about the thrown
     *  exception.
     *
     *  @param msg  the message to log.
     *  @param t    the exception that represents the reported problem.
     */
    public void log(String msg, Throwable t) {
        if (t instanceof Exception) {
            this.getServletContext().log((Exception) t, msg);
        }
    }

    /**
     *  Get the cached provider if there is one corresponding to the providerID
     *  specified by the request parameter (or providerID = -1 if this is a
     *  request for a test page. If there is no cached provider, call start provider
     *  and cache the provider so obtained
     */
    private Provider getProvider(ServletProviderRequest request,
                                              ServletProviderResponse response)
                                              throws ServletException
    {
        if(request.isTest())
        {
            return startProvider(-1, request, response);
        }

        long providerID = request.getProviderId();
        SimpleStringBuffer buffer = new SimpleStringBuffer(100);

        // double-check logic is OK in this situation because we are not dealing 
        // with the construction of a new object. In this case, a new provider
        // may be constructed, but the logic is such that the object reference
        // will be full populated (by a significant number of method calls) 
        // before returning from the getCachedProvider() method
        if (mDebugLevel > 0)
        {
            buffer.setLength(0);
            buffer.append("Provider(");
            buffer.append(providerID);
            buffer.append("): Looking for definition in cache...");
            log(buffer.toString());
        }

        Provider provider = getCachedProvider(providerID);

        if (provider != null)
        {
            if (mDebugLevel > 0)
            {
                buffer.setLength(0);
                buffer.append("Provider(");
                buffer.append(providerID);
                buffer.append("): Definition found in cache...");
                log(buffer.toString());
            }
            
            if (provider instanceof ProviderXtra2 && mAutoReload)
            {
                if (mDebugLevel > 0)
                {
                    buffer.setLength(0);
                    buffer.append("Provider(");
                    buffer.append(providerID);
                    buffer.append("): Checking if reload required...");
                    log(buffer.toString());
                }
                
                ProviderXtra2 px = (ProviderXtra2)provider;
                synchronized(mLock)
                {
                    if (px.isReloadRequired(request.getAction()))
                    {
                        if (mDebugLevel > 0)
                        {
                            buffer.setLength(0);
                            buffer.append("Provider(");
                            buffer.append(providerID);
                            buffer.append("): Reload required. Reloading...");
                            log(buffer.toString());
                        }
                        
                        provider = startProvider(providerID, request, response);

                        // replace the old cache entry with the new one using 
                        // the object's address to determine when we have the 
                        // correct one.  Any threads using the old instance 
                        // will continue to the completion of the request after 
                        // which it will be garbage collected.
                        for (int i=0; i < mProviderCount; i++)
                        {
                            if (mProviders[i].getProviderId() == providerID)
                            {
                                mProviders[i] = provider;
                                break;
                            }
                        }

                        if (mDebugLevel > 0)
                        {
                            buffer.setLength(0);
                            buffer.append("Provider(");
                            buffer.append(providerID);
                            buffer.append("): Reload completed successfully");
                            log(buffer.toString());
                        }
                    }
                    else
                    {
                        if (mDebugLevel > 0)
                        {
                            buffer.setLength(0);
                            buffer.append("Provider(");
                            buffer.append(providerID);
                            buffer.append("): Reload not required");
                            log(buffer.toString());
                        }
                    }
                } // synchronized(mLock)
            }
            else
            {
                if (mDebugLevel > 0)
                {
                    buffer.setLength(0);
                    buffer.append("Provider(");
                    buffer.append(providerID);
                    buffer.append("): Reload not supported by provider");
                    log(buffer.toString());
                }
            }
        }
        else
        {
            // not found in cache
            synchronized(mLock)
            {
                provider = getCachedProvider(providerID);
                if (provider == null)
                {
                    if (mDebugLevel > 0)
                    {
                        buffer.setLength(0);
                        buffer.append("Provider(");
                        buffer.append(providerID);
                        buffer.append("): Definition not found in cache - ");
                        buffer.append("loading definition");
                        log(buffer.toString());
                    }
          
                    provider = startProvider(providerID, request, response);
                    // cache the Provider
                    if (mProviderCount == mProviders.length)
                    {
                        Provider[] temp = new Provider[mProviders.length + 5];
                        System.arraycopy(mProviders, 0, temp, 0, mProviders.length);
                        mProviders = temp;
                    }
                    mProviders[mProviderCount] = provider;
                    mProviderCount++;

                    if (mDebugLevel > 0)
                    {
                        buffer.setLength(0);
                        buffer.append("Provider(");
                        buffer.append(providerID);
                        buffer.append("): Definition loaded successfully");
                        log(buffer.toString());
                    }
                }
            } // synchronized(mLock)
        }
        return provider;
    }            


    /**
     *  Gets the provider class and populates mProviderClass. Instantiates the provider
     *  and initialises it. Reports errors as Soap faults if not in SHOW_PORTLET or
     *  test page mode.
     */
    private Provider startProvider( long providerId,
                                    ServletProviderRequest request,
                                    ServletProviderResponse response)
                                    throws ServletException
    {
      // Decide whether we should be setting a SOAP fault, should an exception
      // occur (not appropriate in SHOW mode)
      boolean SOAPOnError = false;
      if(!request.isTest())
      {
        SOAPOnError = request.getAction() !=
                                  HttpProviderConstants.ACTION_SHOW_PORTLET;
      }
      // get the Provider class here
      Provider provider = null;
      mProviderClass = getProviderClass();
      // instantiate the new Provider instance
      try
      {
        provider = (Provider) mProviderClass.newInstance();
      }
      catch (Exception e)
      {
        String errTxt = "HttpProvider.startProvider: Unable to create new " +
                        "provider instance: " + e.toString();
        log(errTxt, e);

        if (SOAPOnError)
        {
          response.setFault(HttpProviderConstants.SOAP_FAULT_TYPE_SERVER, errTxt);
        }
        else if(!request.isTest())
        {
          response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, errTxt);
        }
        throw new ServletException(errTxt);
      }
      // initialize the newly created Provider instance
      try
      {
        if (provider instanceof Initializable)
        {
            ServletProviderContext context =
                new ServletProviderContext(request.getServletConfig());
            context.putParameter(ProviderConstants.PROVIDER_ID,
                                 new Long(providerId));
            context.putParameter(ProviderConstants.PROVIDER_LOG, this);
            context.putParameter(SERVLET_REQUEST,request.getRequest());
            context.putParameter(ProviderConstants.RELOAD_INTERVAL, 
                                 new Integer(mReloadInterval));
            ((Initializable)provider).init(context);
        }
        else
        {
          provider.init(providerId, this);
        }
      }
      catch (Exception e)
      {
        String errTxt = "HttpProvider.startProvider: Unable to initialize " +
                        "new provider instance: " + e.toString();
        log(errTxt, e);
        
        if (SOAPOnError)
        {
          response.setFault(HttpProviderConstants.SOAP_FAULT_TYPE_SERVER, errTxt);
        }
        else if(!request.isTest())
        {
          response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, errTxt);
        }
        throw new ServletException(errTxt);
      }
      return provider;
    }

    private Provider getCachedProvider(long id) {
        for (int i = 0; i < mProviderCount; i++) {
            if (mProviders[i].getProviderId() == id) return mProviders[i];
        }
        return null;
    }

    private void dispatchProviderAction(int action, 
                                        Provider provider,
                                        ServletProviderResponse response, 
                                        PerformanceLogger logger, 
                                        ServletProviderRequest request) 
    {
        // Set the non-timing members of the logger.
        logger.setAction(action);
        logger.setProviderId(request.getProviderId());

        try
        {
            logger.setUser(request.getUser());
        }
        catch (IllegalStateException ise)
        {
            // No user in the incoming request
        }        
        try
        {
            long portletId = request.getPortletId();
            Portlet p = provider.getPortlet(portletId, request.getProviderUser());
            logger.setPortletId(portletId);
            // Get the name if this user has access
            if (p != null)
            {
                logger.setPortletName(p.getName(request.getLanguage()));
            }
            logger.setPortletInstanceName(request.getPortletName());
        }
        catch (ProviderException pe)
        {
            // Shouldn't happen in normal operation, deal with it properly
            // later if it does.
        }
        catch (IllegalStateException ise)
        {
            // The portlet id and portlet instance name are not in all
            // incoming requests.
        }
                
        // Add this provider thread to the monitor
        PortletExecutionTracker pet = (PortletExecutionTracker)request;
        mProviderThreadMonitor.addThread(pet, Thread.currentThread());
        try
        {
            switch (action) 
            {
            case HttpProviderConstants.ACTION_REGISTER_PROVIDER:
                response.registerProvider(provider);
                break;
            case HttpProviderConstants.ACTION_DEREGISTER_PROVIDER:
                response.deregisterProvider(provider);
                break;
            case HttpProviderConstants.ACTION_GET_PORTLETS:
                response.getPortlets(provider);
                break;
            case HttpProviderConstants.ACTION_GET_PORTLET:
                response.getPortlet(provider);
                break;
            case HttpProviderConstants.ACTION_IS_PORTLET_RUNNABLE:
                response.isPortletRunnable(provider);
                break;
            case HttpProviderConstants.ACTION_GET_API_VERSION:
                response.getAPIVersion(provider);
                break;
            case HttpProviderConstants.ACTION_REGISTER_PORTLET:
                response.registerPortlet(provider);
                break;
            case HttpProviderConstants.ACTION_DEREGISTER_PORTLET:
                response.deregisterPortlet(provider);
                break;
            case HttpProviderConstants.ACTION_COPY_PORTLET:
                response.copyPortlet(provider);
                break;
            case HttpProviderConstants.ACTION_INIT_SESSION:
                response.initSession(provider);
                break;
            case HttpProviderConstants.ACTION_SHOW_PORTLET:       
                response.showPortlet(provider);
                break;
            default:
                response.setFault(HttpProviderConstants.SOAP_FAULT_TYPE_CLIENT, "Unrecognized action.");
                break;
        
            }
        }
        catch (ThreadDeath td)
        {
            // This will be thrown when a request hangs and is stopped by
            // the provider thread monitor. It should be rethrown to be 
            // handled properly by the servlet framework.
            logger.writeLog(this);
            throw td;            
        }            
        
        // Request completed so remove it from the monitor.
        mProviderThreadMonitor.removeThread(pet);
    }

    private String translateException(Throwable t) {
        CharArrayWriter c = new CharArrayWriter();
        t.printStackTrace(new PrintWriter(c));
        return c.toString();
    }

}

