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

package oracle.portal.provider.v1.http;

import java.lang.reflect.*;
import java.io.*;
import java.text.*;
import java.util.*;
import java.net.URL;

import javax.servlet.*;
import javax.servlet.http.*;

import oracle.portal.provider.v1.*;
import oracle.portal.utils.v1.*;
import oracle.portal.utils.xml.v1.*;
import oracle.webdb.utils.SimpleStringBuffer;

import org.w3c.dom.*;
import org.w3c.dom.Node;
import org.xml.sax.*;
import oracle.xml.parser.v2.XMLDocument;
import oracle.xml.parser.v2.XSLException;
import oracle.xml.parser.v2.XSLStylesheet;

/**
 * The DefaultProvider is the JPDKs base implementation class for the Provider
 * interface.  It not only implements default/common behaviors for the Provider's
 * method, but as importantly, is responsible for bootstrapping all the portlets
 * and their components.  This later function is managed by processing the
 * provider.xml file which describes the characteristics of each portlet
 * managed by this provider.<p>
 * When the DefaultProvider is instantiated by the HttpProvider servlet, the
 * instance processes the provider.xml file.  For each portlet (and controller)
 * described in the file, the DefaultProvider creates the instance and pushes
 * the corresponding meta and configuration data.
 */
public class DefaultProvider implements ProviderXtra2, Initializable 
{

    private static final String SERVLET_APPDIR = "WEB-INF";
    private static final String PROVIDER_REPOSITORY_ROOT = "provider_root";
    private static final String PROVIDER_REGISTRY_PARAM = "provider_definition";
    private static final String PROVIDER_DEFAULT_REGISTRY = "provider.xml";

    // The full version string for the registry data format
    private static final String PROVIDER_XML_FORMAT_VERSION = "2.0";

    // The 'major' version from the above string
    private static final int    PROVIDER_XML_FORMAT_MAJOR_VERSION =
        XMLUtil.getVersionMajor(PROVIDER_XML_FORMAT_VERSION);

    private static final String PROVIDER_UPGRADE_V1_STYLESHEET = "upgrade_v1_to_v2.xsl";
    

    private static final String PI_PROVIDER = "providerDefinition";
    private static final String DATA_VERSION_ATTRIBUTE = "version";
    private static final String ELEMENT_PROVIDER = "provider";
    private static final String ATTRIBUTE_CLASS = "class";

    private long mProviderId;
    private ProviderLog mLog;
    private Vector mPortlets = new Vector(10);
    private String mRepositoryRealPath;
    private String mRegistryName;
    private ServletContext mServletContext;
    private boolean mEnableSession = true;
    private boolean mUseOldStyleHeaders = false;
    private ParameterMap mContext;
    private PortletContainerRenderer mDefaultContainerRenderer;
    private int mDebugLevel = 0;
    private Object mLock = new Object();

    /*
    * variables controlling auto-reload of the provider definition
    */
    // time registry actually loaded - compared against file modification date
    private long mRegistryLoaded = 0;
    // time last check was performed - used to determine when to do the next check
    private long mRegistryLastChecked = 0;
    // interval between checks
    private long mReloadInterval = 300000; // 5 minutes

    protected static String getProviderClassFromXml(HttpServlet servlet)throws ProviderException
    {
      String providerRoot = servlet.getInitParameter(PROVIDER_REPOSITORY_ROOT);
      String registryName = getProviderRegistryName(servlet.getServletConfig());
      InputStream is = null;
      if (providerRoot != null)
      {
        try
        {
          // parse the document, and apply any necessary transformations
          // to it
          Element docEl = parseRegistry(providerRoot, registryName).getDocumentElement();
          // Get the class name from the document element (we have
          // already validated that <provider> is the document element)
          String className = docEl.getAttribute(ATTRIBUTE_CLASS);
          // if no class attribute then infer the default which is this class
          if (className == null || className.length() == 0)
          {
            return DefaultProvider.class.getName();
          }
          else
          {
            return className;
          }
        }
        finally
        {
          try
          {
            if (is != null)
              is.close();
          }
          catch (Exception ae)
          {
          }
        }
      }
      return null;
    }

    /**
     *  Initialize the provider.  Simpliest use is to just set the data members
     *  (ProviderID and logger) that correspond to this new instantiated provider.
     *  It is also valid to register the provider if new or "acquire" the provider if
     *  it already exists.  Though in the former case the register() method should
     *  ultimately be called and the work done there.  The DefaultProvider processes
     *  the provider.xml file here, bootstrapping the portlets it manages.
     *
     *  @param  providerId  the portal provider identifier this instance represents.
     *  @param  logger the system the Provider uses to log error and status messages
     *  to.
     */
    public void init(long providerId, ProviderLog logger) throws ProviderException {
        mPortlets.removeAllElements();
        mProviderId = providerId;
        mLog = logger;
    }

    /**
     * Performs the steps necessary to fully initialize this
     * <code>DefaultProvider</code>.
     *
     * @param parameters a {@link ParameterMap} containing all parameters
     * necessary to initialize the object.
     * @exception ProviderException if an error occurs whilst trying to
     * initialize.
     */
    public void init(ParameterMap param) throws ProviderException
    {
        if ( !param.containsParameter(ProviderConstants.PROVIDER_ID) ||
             !param.containsParameter(ProviderConstants.PROVIDER_LOG) ||
             !param.containsParameter(HttpProvider.SERVLET_CONFIG) ||
             !param.containsParameter(HttpProvider.SERVLET_REQUEST))
        {
            throw new ProviderException("Provider did not recieve necessary " +
                                        "ititialization information");
        }
        mContext = param;

        Integer reloadInterval = 
            (Integer)param.getParameterValue(ProviderConstants.RELOAD_INTERVAL);
        if (reloadInterval != null)
        {
            mReloadInterval = reloadInterval.intValue();
        }

        init(((Long)param.getParameterValue(ProviderConstants.PROVIDER_ID)).longValue(),
             (ProviderLog)param.getParameterValue(ProviderConstants.PROVIDER_LOG),
             (ServletConfig)param.getParameterValue(HttpProvider.SERVLET_CONFIG),
             (HttpServletRequest)param.getParameterValue(HttpProvider.SERVLET_REQUEST));
    }


    /**
     * Gets the {@link oracle.portal.utils.v1.ParameterMap} which contains the
     * {@link oracle.portal.provider.v1.Provider}'s contextual information.
     */
    public ParameterMap getProviderContext()
    {
        return mContext;
    }

    protected void init(long providerId, ProviderLog logger, ServletConfig sc, HttpServletRequest request) throws ProviderException {
        init(providerId, logger);

        if (sc == null) return;

        mServletContext = sc.getServletContext();

        XMLDocument registryXML = null;
        String vp = getProviderRepositoryPath(sc);
        if (vp == null) {
            mRepositoryRealPath = getProviderRepositoryRealPath(sc);
        } else {
            mRepositoryRealPath = mServletContext.getRealPath(vp);
        }
        // Store the name of the provider registry (defaults to provider.xml,
        // may be overriden in provider_definition init param)
        mRegistryName = getProviderRegistryName(sc);

        // Try parsing the document and applying any necessary transformations
        // to it. Log any exceptions that occur.
        try {
            registryXML = parseRegistry(mRepositoryRealPath, mRegistryName);
        } catch (ProviderException pe) {
            log(pe.getMessage(), pe);
            throw pe;
        }

        // Check initParameters to see if debugMode is on
        String debugStr = sc.getInitParameter(HttpProviderConstants.PROVIDER_DEBUG_LEVEL);
        if (debugStr != null) {
            mDebugLevel = Integer.parseInt(debugStr);
        }

        // Output the transformed XML document if debugLevel >= 2
        if (mDebugLevel >= 2) {
            OutputStream bos = new ByteArrayOutputStream();
            log("Traverse provider.xml document tree: ");
            try {
                registryXML.print(bos);
                bos.close();
            } catch (IOException e) {
            }
            log(bos.toString());
        }            

        // Now Process the Registry
        NodeHandler nodeHandler = new ProviderNodeHandler();
        try {
            nodeHandler.processNode(registryXML.getDocumentElement(),this);
        } catch (NodeHandlerException e) {
            throw new ProviderException(e);
        }

        // Finally, validate the registry, and note when we finished
        validateRegistry();
        mRegistryLoaded = System.currentTimeMillis();
        mRegistryLastChecked = mRegistryLoaded;
    }

    /**
     * Specifies whether or not PortletRendererUtil should render headers and
     * footers for this provider's portlets in the style used by a 3.0.6 portal
     * or earlier, rather than the style used by a post-3.0.6 portal.
     *
     * @param useOldStyleHeaders boolean indicating which style of form headers
     * and footers should be rendered by PortletRendererUtil.<code>true</code>
     * - render portal 3.0.6 style headers and footers. <code>false</code> -
     * render new style headers and footers.
     *
     * @deprecated use <code>setContainerRenderer(new ContainerRenderer306())
     *             </code> to get equivalent behavior. Alternatively, create
     *             your own container renderer by extending
     *             <code>PortletContainerRenderer</code>
     */
    public void setUseOldStyleHeaders(boolean useOldStyleHeaders)
    {
        mUseOldStyleHeaders = useOldStyleHeaders;
    }

    /**
     * Specifies whether or not PortletRendererUtil should render headers and
     * footers for this provider's portlets in the style used by a 3.0.6 portal
     * or earlier, rather than the style used by a post-3.0.6 portal.
     *
     * @param useOldStyleHeaders String representation of boolean ("true" or
     * "false") indicating which style of form headers and footers should be
     * rendered by PortletRendererUtil.
     * @see #setUseOldStyleHeaders(boolean)
     * 
     * @deprecated use <code>setContainerRenderer(new ContainerRenderer306())
     *             </code> to get equivalent behavior. Alternatively, create
     *             your own container renderer by extending
     *             <code>PortletContainerRenderer</code>
     */
    public void setUseOldStyleHeaders(String useOldStyleHeaders)
    {
        setUseOldStyleHeaders(XMLUtil.getBooleanValue(useOldStyleHeaders));
    }

    /**
     * Signals whether PortletRendererUtil will render headers and footers for
     * this provider's portlets in the style used by a 3.0.6 portal or earlier,
     * rather than the style used by a post-3.0.6 portal.
     *
     * @return boolean indicating which style of form headers and footers
     * should be rendered by PortletRendererUtil.<code>true</code> - render
     * portal 3.0.6 style headers and footers. <code>false</code> - render new
     * style headers and footers.
     *
     * @deprecated use get/setContainerRenderer() to specify the default
     *             container renderer that should be used to render portlet
     *             containers for this provider
     */
    public boolean getUseOldStyleHeaders()
    {
        return mUseOldStyleHeaders;
    }

    /**
     * Called during the processing of provider.xml to associate a portlet as a
     * child of this provider.
     * @param portlet portlet to be added to this provider. Should implement
     * the {@link oracle.portal.provider.v1.Portlet} interface.
     * @exception ProviderException if the given object is not a valid {@link
     * oracle.portal.provider.v1.Portlet}
     */
    public void addPortlet(Object portlet) throws ProviderException
    {
        if (!(portlet instanceof Portlet))
  
        {
            throw new ProviderException("Object not a Portlet");
        }
        mPortlets.addElement((Portlet)portlet);
    }

    /**
     * Controls whether this provider starts a (servlet) session in its {@link
     * #initSession(ProviderUser,ExternalPrincipal)} method.
     * @param session String representation of boolean value. Should be "true"
     * if this provider is to start a session in <code>initSession</code>, or
     * "false" otherwise.
     */     
    public void setSession(String session)
    {
        mEnableSession = XMLUtil.getBooleanValue(session);
    }

    /**
     * Gets the default storage path used for meta data associated with this
     * provider, if any
     *
     * @return the filesystem path to the directory containing provider.xml
     */
    public String getProviderRepositoryPath() {
        return mRepositoryRealPath;
    }


    protected String getProviderRepositoryPath(ServletConfig sc) {
        // clients should override
        return null;
    }

    protected String getProviderRepositoryRealPath(ServletConfig sc) {
        // clients should override
        return sc.getInitParameter(PROVIDER_REPOSITORY_ROOT);
    }

    protected String getProviderRegistryName() {
        return mRegistryName;
    }

    private static String getProviderRegistryName(ServletConfig sc) {
        String name = sc.getInitParameter(PROVIDER_REGISTRY_PARAM);
        if(name == null) {
            name = PROVIDER_DEFAULT_REGISTRY;
        }
        return name;
    }

    private static InputStream getDocumentInputStream(String root, String name) 
        throws FileNotFoundException 
    {
        if (root == null) throw new FileNotFoundException();

        return new FileInputStream(new File(root, name));
    }

    /**
     *  Returns this provider's ID.
     */
    public long getProviderId() {
        return mProviderId;
    }

    /**
     *  Provider's version -- version of the PDK API it implements
     */
    public int getVersion() {
        return 1;
    }

    /**
     *  Returns the subscriber for this portal.  In a subscription-based model
     *  a company may want/need to associate its own ID with a customer/portal.  This ID
     *  could be used for billing purposes, for example.  When a Provider is
     *  registered in a Portal, the subscriber ID is entered by the administrator and
     *  passed to the Provider in the register method.  Once established, the Provider
     *  is expected to maintain the relationship between the ProviderId and the
     *  subscriberID (persistently).
     *
     *  The Default Provider doesn't support maintaining a persistant association
     *  between the subscriptionID and providerId, hence it always returns <code>null</code>.
     *  Subclass and override if you need this behavior.
     *
     *  @return the subscriber.  <code>null</code> if this Provider doesn't use/support
     *  subscription.
     */
    public ProviderSubscriber getSubscriber() {
        return null;
    }

    /**
     *  Register this newly created provider.  Having your register method
     *  called means your Provider has just been installed/activated in
     *  a portal.  It is assumed the (new) providerID has already been made
     *  available via init();<p>
     *  In response to this method call, the DefaultProvider enumerates its Portlets
     *  and for each Portlet that has a PersonalizationManager, it invokes
     *  the PersonalizationManager init(Portlet p) method.  This gives the
     *  Portlet's PersonalizationManagers the opportunity to do one time
     *  initialization.<p>
     *  Recognizing that many providers want a lightweight mechanism for
     *  securing the communication between them and the portal, register has
     *  an additional optional function.  In addition to SSL/HTTPS support, the portal
     *  supports a simple key encryption mechanism.  In this case, the administrator
     *  is expected to receive the encryption key (along with a subscriber id) from
     *  the organization providing the service.  The subscriber ID and the encryption
     *  key are entered when the administrator registers the provider in the portal.
     *  All communication between the portal and the provider are encrypted using
     *  this key.<p>
     *  The major drawback of this facility is the service provider must provide
     *  a mechanism for supplying the key in the first place.  When a service
     *  provider merely wants a more secure communication link between the portal
     *  and itself but doesn't require complete/guaranteed security, we allow the
     *  the provider to return the subscriber ID and encryption key from the register
     *  method.  Though, obviously, this communication occurs in open text and can
     *  therefore be compromised, it ensures that all subsequent communication
     *  is encrypted, greatly reducing the opportunity for misbehavior.<p>
     *  The DefaultProvider doesn't support using encryption keys.  Hence it always
     *  returns <code>null</code>.  Subclass and override to add this behavior.
     *
     *  @param subscriberId  Provider defined key customer supplies when the provider
     *  is registered in the portal.  It is expected the Provider will use this
     *  key information to help provision this instance.
     *  @param portalVersion  the version number of the portal you are being registered with.
     *  @return the dynamically generated subscriber containing both the subscriberId and the
     *  encryption key.  This value should only be non-null if the key/subscriber is dynamically
     *  generated.  <code>null</code> should be returned if the encryption key is entered by the
     *  administrator when registering the Provider or encryption isn't used.  Currently,
     *  the DefaultProvider doesn't implement encryption support, hence always returns null.
     *
     */
    public ProviderSubscriber register(String subscriberId, String portalVersion) throws ProviderException {
        // create the portlet repository
        Enumeration portletEnum = mPortlets.elements();
        while(portletEnum.hasMoreElements()) {
            Portlet portlet = (Portlet)portletEnum.nextElement(); 
            PortletPersonalizationManager pm = portlet.getPersonalizationManager();

            if (pm != null) {
                pm.init(portlet);
            }
        }

        return null;
    }

    /**
     *  Deregister this provider.  A Provider's deregister method is called when the
     *  Provider is removed from a portal.  In response to this, the DefaultProvider
     *  calls destroy(Portlet p) on each PersonalizationManager used by its Portlets.
     *  Calling destroy() allows each Personalization manager to remove or cleanup its
     *  repository.  For example it can delete all entries associated with this
     *  provider (Id).
     */
    public void deregister() throws ProviderException {
        // remove the portlet repository

        Exception lastFail = null;
        Enumeration portletEnum = mPortlets.elements();
        while(portletEnum.hasMoreElements()) {
            Portlet portlet = (Portlet)portletEnum.nextElement();
            PortletPersonalizationManager pm = portlet.getPersonalizationManager();

            if (pm != null) {
                try {
                    pm.destroy(portlet);
                } catch (Exception e) {
                    // log this error and then stash the Exception -- so we can
                    // continue with the others
                    log("Failed to destroy portlet personalization repository", e);
                    lastFail = e;
                }
            }
        }

        // Report error if any occurred
        if (lastFail != null) {
            throw new ProviderException(lastFail);
        }
    }

    /**
     *  Called to initialize the Provider in a particular user session. This
     *  may be called never, once per user session, or once per request
     *  depending on how the provider is configured in the portal. If the
     *  Provider uses HTTP cookies or maintains state via a cookie-based HTTP
     *  session, it is imperative that (all) cookies be established during this
     *  call.<p>
     *
     *  A session is established via the ProviderUser user object. I.e. the
     *  user object contains the methods for creating and retrieving a
     *  session. For HttpProviders, further HTTP-specific information can be
     *  accessed by casting the user object to a {@link
     *  oracle.portal.provider.v1.http.ServletProviderUser}. In particular,
     *  this allows access to any existing Cookies already established between
     *  the user and provider, via the {@link
     *  oracle.portal.provider.v1.http.ServletProviderUser#getCookies()}
     *  method.<p>
     *
     *  As this interface is designed for use in both servlet and non-serlvet
     *  environments servlet interfaces could not be used. initSession returns
     *  an array of cookie objects of the type expected in the particular
     *  framework the provider is executing in. For HttpProviders this
     *  <i>is</i> servlets and hence the returned object array should contain
     *  objects of type javax.servlet.http.Cookie.<p>
     *  
     *  Note: in order to support backwards compatibility for providers that
     *  subclass the DefaultProvider and override <code>initSession()</code>,
     *  the current implementation of this method merely turns around and calls
     *  the old deprecated form of <code>initSession()</code>.  This ensures
     *  that subclasses with existing overrides continue to work unchanged.  If
     *  you have implemented such a subclass consider this when you "upgrade"
     *  to the new form of <code>initSession()</code>.  I.e. if your
     *  <code>initSession()</code> (new form) calls <code>super()</code>
     *  remember this will in turn call the old form of
     *  <code>initSession()</code>.  If you haven't removed your old form or
     *  modified its implementation your code could redo/overwrite work you
     *  have already done in the new form of <code>initSession</code>.  Our
     *  recommendation is to remove your existing (old form)
     *  <code>initSession()</code> when upgrading to the new form.  This means
     *  all will continue to work correctly when we remove the deprecated old
     *  form in the future.
     *
     *  @param user Current portal user.
     *  @param externalApp External Application information (if it exists).
     *  @return Array of cookie objects.  <b>null</b> if this provider doesn't
     *  use cookies or relies on the (user) session to maintain its state.
     *  The type of these objects is specific
     *  to the environment in which this provider runs.  When running in the
     *  servlet environment (HttpProviders) these objects should be of type
     *  javax.servlet.http.Cookie.
     *  @exception ProviderException ProviderException is a generic exception
     *  for Providers.
     *  @exception AuthenticationException AuthenticationException should be
     *  thrown if you have an external application that requires additional
     *  authentication and that authentication fails for some reason.
     */
    public Object[] initSession(ProviderUser user, ExternalPrincipal externalApp) 
           throws ProviderException
    {
        // we call the deprecated form rather then vice versa to ensure that
        // existing subclasses that override this method continue to work unchanged.
        // Remove this and do the real intialization of the session when
        // we finally remove the deprecated method.
        return initSession(user);
    }

    /**
     *  Called to initialize the Provider in a particular user session.  This
     *  may be called never, once per user session, or once per request depending
     *  on how the provider is configured in the portal.  If the Provider uses
     *  HTTP cookies or maintains state via a cookie-based HTTP session, it is
     *  imperative that (all) cookies be established during this call. <p>
     *  A session is established via the user object.  I.e. the user object
     *  contains the methods for creating and retrieving a session.<p>As this
     *  interface is designed for use in both servlet and non-serlvet environments
     *  servlet interfaces could not be used. initSession returns an array of
     *  cookie objects of the type expected in the particular framework the
     *  provider is executing in.  For HttpProviders this <i>is</i> servlets and
     *  hence the returned object array should contain object of type
     *  javax.servlet.http.Cookie.
     *
     *  @deprecated  As of Portal 3.0.8 use initSession that receives the
     *  externalApp parameter.<p>
     *  @param user  Current portal user.
     *  @return Array of cookie objects.  <b>null</b> if this provider doesn't
     *  use cookies or relies on the (user) session to maintain its state.
     *  The type of these objects is specific
     *  to the environment in which this provider runs.  When running in the
     *  servlet environment (HttpProviders) these objects should be of type
     *  javax.servlet.http.Cookie.
     *  @exception ProviderException ProviderException is a generic exception
     *  for Providers
     */
    public Object[] initSession(ProviderUser user) throws ProviderException {
        // By default create an HttpSession -- this will create a cookie.
        if (mEnableSession) {
            ProviderSession session = user.getSession(true);
        }
        return null;
    }

    /**
     * Returns array of Portlets managed by this Provider.  The range of
     * Portlets being requested are controlled by <code>start</code> and <code>count</code>.
     * Providers should return only <code>count</code> Portlets starting at <code>start</code>.
     * If there are fewer then <code>count</code> Portlets then a shorter array is
     * returned.<p>
     * The Portlets returned by getPortlets() are constrained to those the user
     * is authorized to access if <code>byUser</code> is true.<p>
     * The DefaultProvider implements these semantics based on the set of portlets
     * registered with it via the provider.xml descriptor file.
     *
     * @param start The ordinal of the first portlet that should be returned.
     * @param count The maximum number of portlets that should be returned.
     * @param byUser If true, indicates the returned list is constrained to those
     * portlets the user is authorized to access.
     * @param user The current user on whose behalf the request is being made.
     * @return array of portlets constrained to the indicated range.
     */
    public Portlet[] getPortlets(int start, 
                                 int count, 
                                 boolean byUser, 
                                 ProviderUser user)
        throws ProviderException 
    {
        if (isDebugEnabled())
        {
            log("DefaultProvider.getPortlets() - ProviderId = " + mProviderId);
            log(" - start index = " + start);
            log(" - count = " + count);
            log(" - byUser = " + byUser);
            if (user != null)
            {
                log(" - User = " + user.getName());
            }
            else
            {
                log(" - No User Specified");
            }
        }
               
        int size = Math.min(count, mPortlets.size());
        Portlet[] p = new Portlet[size];
        int elementsCopied = 0;
        int s = 0;
        Enumeration i = mPortlets.elements();
        while(i.hasMoreElements() && elementsCopied < size) 
        {
            Portlet portlet = (Portlet)i.nextElement();
            if (byUser) 
            {
                PortletSecurityManager authMgr = portlet.getSecurityManager();
                if (authMgr != null && !authMgr.hasAccess(portlet, user)) 
                {
                    if (isDebugEnabled())
                    {
                        log("DefaultProvider.getPortlets() - Access Denied by SecurityManager");
                        log(" - portlet id: " + portlet.getId());
                        log(" - portlet name: " + 
                            portlet.getName(portlet.getDefaultLocale()));
                    }
                    continue;
                }
            }

            if (s < start) 
            {
                if (isDebugEnabled())
                {
                    log("DefaultProvider.getPortlets() - portlet index " +
                        "less than start index - skipping portlet: id = " +
                        portlet.getId());
                }
                        
                s++;
                continue;
            }
            p[elementsCopied++] = portlet;
        }

        if (isDebugEnabled())
        {
            log("DefaultProvider.getPortlets() - returning " +
                elementsCopied + " of " + mPortlets.size() +
                " portlets");
        }

        if (elementsCopied == p.length)
        {
            // all portlets are accessible
            return p;
        }
        else if (elementsCopied > 0)
        {
            // array p is not full.  This means the user did not have access
            // to all the portlets from this provider.
            // Create a smaller array to contain the accessible portlets
            Portlet[] p1 = new Portlet[elementsCopied];
            System.arraycopy(p,0,p1,0,elementsCopied);
            return p1;
        }
        else
        {
            // no portlets are accessible by the user
            return null;
        }
    }



    /**
     *  Returns the Portlet with the given ID.  The Portlet should only be returned
     *  if the user is authorized to access it.<p>
     *  The DefaultProvider implements this semantic based on the set of portlets
     *  registered with it via the provider.xml descriptor file.
     *
     *  @param id  Portlet Id.
     *  @param user Current user.
     *  @return the Portlet associated with the given Id.  <code>null</code> if no portlet
     *  matches that Id or if the user isn't authorized to access the portlet.
     */
    public Portlet getPortlet(long id, ProviderUser user) throws ProviderException {
        Enumeration portletEnum = mPortlets.elements();
        while( portletEnum.hasMoreElements()) {
            Portlet portlet = (Portlet)portletEnum.nextElement();
            if (portlet.getId() == id) {
                // make sure this user has access
                PortletSecurityManager authMgr = portlet.getSecurityManager();
                if (authMgr == null || authMgr.hasAccess(portlet, user)) {
                    return portlet;
                } else {
                    return null;
                }
            }
        }
        
        SimpleStringBuffer buf = new SimpleStringBuffer(100);
        buf.append("ERROR: Portlet with id ");
        buf.append(id);
        buf.append(" does not exist in provider ");
        buf.append(getProviderId());
        log(buf.toString());
        
        throw new ProviderException(buf.toString());
    }

    /**
     *
     * Writes the specified message to a provider log, which is usually
     * an event log. The message provides explanatory information about
     * an exception or error or an action the provider takes. The name
     * and type of the provider log file is specific to the provider engine.
     * For Provider's running in the servlet environment, the log is the
     * servlet log.
     *
     *
     * @param msg   a <code>String</code> specifying the explanatory
     *      message to be written to the log file
     *
     */

    public void log(String msg) {
        if (mLog != null) {
            mLog.log(msg);
        }
    }

    /**
     * Writes the stack trace and an explanatory message
     * for a given <code>Throwable</code> exception
     * to the provider log file. The stack trace is
     * part of the <code>Throwable</code> object, and
     * the message is the one you specify in the <code>message</code>
     * parameter. The name and type of the provider log file is specific to
     * the provider engine, but it is usually an event log.
     *
     *
     * @param message     a <code>String</code> that
     *        describes the error or exception
     *
     * @param throwable   the <code>Throwable</code> error
     *        or exception
     *
     */
    public void log(String message, Throwable throwable) {
        if (mLog != null) {
            mLog.log(message, throwable);
        }
    }

    /**
     * Returns the default portlet container renderer
     *
     * The portlet container renderer is responsible for rendering the portlet
     * container in each of the show modes the portlet supports.
     *
     * Each portlet can specify it's own container renderer and you can specify
     * a default at the provider level to be used by any portlet that does
     * not explicitly specify it's own container renderer
     *
     * @return the portlet container renderer or null if the provider does
     *         not have a default container renderer
     */
    public PortletContainerRenderer getContainerRenderer()
    {
        if (mDefaultContainerRenderer != null)
            return mDefaultContainerRenderer;

        // since this method is called during the processing of portlet requests
        // and it modifies the state of the PortletDefinition (which is shared)
        // this portion of the code should be synchronized
        synchronized(mLock)
        {
            if (getUseOldStyleHeaders())
            {
                mDefaultContainerRenderer = new ContainerRenderer306();
            }
            else
            {
                mDefaultContainerRenderer = new DefaultContainerRenderer();
            }
        }
        return mDefaultContainerRenderer;
    }


    /**
     * Sets the default portlet container renderer
     *
     * The container renderer is responsible for rendering the portlet
     * container in each of the show modes the portlet supports
     *
     * Each portlet can specify it's own container renderer and you can specify
     * a default at the provider level to be used by any portlet that does
     * not explicitly specify it's own container renderer
     *
     * @param renderer the portlet container renderer
     */
    public void setContainerRenderer(PortletContainerRenderer renderer)
        throws ProviderException
    {
        mDefaultContainerRenderer = renderer;
    }


    /**
     * Sets the default portlet container renderer
     *
     * The container renderer is responsible for rendering the portlet
     * container in each of the show modes the portlet supports
     *
     * Each portlet can specify it's own container renderer and you can specify
     * a default at the provider level to be used by any portlet that does
     * not explicitly specify it's own container renderer
     *
     * @param renderer the default portlet container renderer. Even though the
     *                 type of the parameter is Object, the parameter must be an
     *                 instance of PortletContainerRenderer
     */
    public void setContainerRenderer(Object renderer) throws ProviderException
    {
        if (renderer != null && renderer instanceof PortletContainerRenderer)
        {
            setContainerRenderer((PortletContainerRenderer)renderer);
        }
        else
        {
            throw new ProviderException("Object must be an instance of PortletContainerRenderer");
        }
    }


    /**
    * Used to determine if this provider needs to be re-initialized from the
    * provider.xml file.  
    * 
    * <p>The mechanism used should work when providers are load-balanced. This
    * means it cannot be drived by communication from the portal such as a 
    * repository refresh (as it used
    * to be) because only one server will receive the refresh request and 
    * reloaded the definition. This leads to the servers being out of sync.</p>
    * 
    * <p>The simplest way to do this is to trigger reload (or at least a 
    * modification check) on a time interval.</p> 
    * This feature requires auto-reload to be enabled.
    * @param action not currently used
    * @returns true if the provider.xml needs to be reloaded.
    */
    public boolean isReloadRequired(int action)
    {
        long currentTime = System.currentTimeMillis();
        if (currentTime > (mRegistryLastChecked + mReloadInterval))
        {
                 mRegistryLastChecked = currentTime;
                return isRegistryModified();
        }
        return false;
    }
    

    /**
    * Returns true if debug mode is enabled.
    *
    * Debug mode enabled is determined by the Debug Level being greater than zero
    * 
    * @returns true if debug mode is enabled. Otherwise false
    */
    public boolean isDebugEnabled()
    {
        return mDebugLevel > 0;
    }

    
    /**
    * Returns the current debug level.
    * 
    * @returns the current debug level
    */
    public int getDebugLevel()
    {
        return mDebugLevel;
    }


    /**
    * Returns the ProviderLog associated with this Provider.
    * 
    * @returns the ProviderLog associated with this Povider
    */
    public ProviderLog getProviderLog()
    {
        return mLog;
    }

    
    private boolean isRegistryModified()
    {
        String path = getProviderRepositoryPath();
        String name = getProviderRegistryName();
        if (path == null || name == null) {
            // something is very wrong if the repository path is not set up
            // so force a reload
            return true;
        }

        File providerXml = new File(path, name);

        // return true if the provider.xml file has been updated since this
        // provider was started.
        if (providerXml.lastModified() > mRegistryLoaded)
            return true;

        return false;
    }

    private void validateRegistry() throws ProviderException {
        for (int i = 0; i < mPortlets.size(); i++) {
            validateID(i);
            validateRenderer(i);
        }
    }

    private void validateID(int i) throws ProviderException {
        long id = ((Portlet)mPortlets.elementAt(i)).getId();
        if (id == 0) {
            throw new ProviderException(getName(i) + ": Required ID value not set.");
        } else if (id < 0) {
            throw new ProviderException(getName(i) + ": Invalid Portlet ID " + id + ".");
        }
        for (int j = 0; j < mPortlets.size(); j++) {
            if (j == i) continue;
            if (id == ((Portlet)mPortlets.elementAt(j)).getId()) {
                throw new ProviderException(getName(i) + ": doesn't have a unique ID.  Its ID " + id + " matches " + getName(j));
            }
        }
    }

    private void validateRenderer(int i) throws ProviderException {
        if (((Portlet)mPortlets.elementAt(i)).getRenderer() == null) {
            throw new ProviderException(getName(i) + ": Required Renderer not set.");
        }
    }

    private String getName(int i) {
        Portlet portlet = (Portlet)mPortlets.elementAt(i);
        String name = portlet.getName(portlet.getDefaultLocale());
        if (name != null) {
            name = "Portlet " + name;
        } else {
            name = "Portlet #" + i;
        }
        return name;
    }

    private static XMLDocument parseRegistry(String repositoryPath, String registryName) throws ProviderException
    {
      XMLDocument xmldoc = null;
      //Capture any XML / XSL parsing errors in the following block to this
      //output stream. A finally{} block at the end ensures it gets closed
      //afterwards
      ByteArrayOutputStream errOut = new ByteArrayOutputStream();
      try
      {
        // Make sure we release this InputStream when finished with it
        InputStream is = null;
        try
        {
          is = getDocumentInputStream(repositoryPath, registryName);
          // Obtain the document.
          try
          {
            xmldoc = XMLUtil.parseDocument(is, errOut);
          }
          catch(SAXParseException e1)
          {
            String errtext = "Error parsing " + registryName + " at line "
                + e1.getLineNumber() + ", column " + e1.getColumnNumber();
            throw new ProviderException(errtext);

          }
        }
        finally
        {
          if(is != null)
          {
            try
            {
              is.close();
            }
            catch(Exception e1)
            {
            }
            is = null;
          }
        }
        Enumeration xsldocs = XMLUtil.getXslDocs(xmldoc);
        // If the document explicitly names a list of stylesheets, then
        // apply them
        if(xsldocs.hasMoreElements())
        {
          //Apply the XSL transformations to the xml document.
          do
          {
            xmldoc = XMLUtil.applyXSL(xmldoc,
            (XSLStylesheet)xsldocs.nextElement(), errOut);
          }
          while(xsldocs.hasMoreElements());
        }
        // If there are no stylesheets explicitly named, check for a
        // version processing instruction
        else
        {
          String version = null;
          // Assume we have a version 1 format file, unless we can find a
          // providerDefinition processing instruction with a version
          // specified.
          int majorVersion = 1;
          Enumeration pdkpis = XMLUtil.getTargetPIs(xmldoc, PI_PROVIDER);
          if(pdkpis.hasMoreElements())
          {
            ProcessingInstruction pi = (ProcessingInstruction)pdkpis.nextElement();
            Hashtable piAttributes = XMLUtil.parsePI(pi);
            // Try to get a version number out of the processing instruction
            version = (String)piAttributes.get(DATA_VERSION_ATTRIBUTE);
            if(version != null)
            {
              try
              {
                majorVersion = XMLUtil.getVersionMajor(version);
              }
              catch (NumberFormatException nfe)
              {
                throw new ProviderException("XML processing error: Invalid version number specified in '" +
                                                        PI_PROVIDER + "' processing instruction.");
              }
            }
          }
          // Upgrade the xml, if it is below version 2
          if (majorVersion < PROVIDER_XML_FORMAT_MAJOR_VERSION)
          {
            // Get the upgrade stylesheet from within the .jar file on
            // the classpath
            XMLDocument upgragedoc = null;
            // Make sure we release the InputStream when finished with it
            try
            {
              is = DefaultProvider.class.getResourceAsStream("resource/" + PROVIDER_UPGRADE_V1_STYLESHEET);
              upgragedoc = XMLUtil.parseDocument(is, errOut);
            }
            finally
            {
              if(is != null)
              {
                try
                {
                  is.close();
                }
                catch(Exception e1)
                {
                }
              }
              is = null;
            }
            XSLStylesheet upgradeXSL = new XSLStylesheet(upgragedoc, new URL("http://www.oracle.com/portal/"));
            xmldoc = XMLUtil.applyXSL(xmldoc, upgradeXSL, errOut);
          }
          // Throw an exception if we have a data format we can't cope with
          else if(version != null && !version.equals(PROVIDER_XML_FORMAT_VERSION))
          {
            throw new ProviderException("Error: this provider does not support XML registries with a " +
                                                PI_PROVIDER + " version above \"" + PROVIDER_XML_FORMAT_VERSION + "\"");
          }
        }
      } // End try
      catch (SAXException se)
      {
        String msg = se.getMessage() + Character.LINE_SEPARATOR + errOut.toString();
        throw new ProviderException(msg);
      }
      catch (XSLException xsle)
      {
        String msg = xsle.getMessage() + Character.LINE_SEPARATOR + errOut.toString();
        throw new ProviderException(msg);
      }
      catch (IOException ioe)
      {
        throw new ProviderException("IO Error parsing provider registry: " + ioe.getMessage());
      }
      finally
      {
        if(errOut!= null)
        {
          try
          {
            errOut.close();
          }
          catch(Exception e1)
          {
          }
        }
        errOut = null;
      }
      // Make sure that the XML we end up with actually has <provider> at the root!
      Element docEl = xmldoc.getDocumentElement();
      if(docEl == null || !docEl.getTagName().equalsIgnoreCase(ELEMENT_PROVIDER))
      {
        throw new ProviderException("No provider element found in provider.xml");
      }
      return xmldoc;
    }
  }
