/*  Copyright (c) 2000, 2001, 2003, Oracle Corporation. All Rights Reserved. */

package oracle.portal.provider.v2;

import java.io.IOException;
import java.util.*;
import oracle.portal.log.LogManager;
import oracle.portal.provider.v2.externalApp.ExternalPrincipal;
import oracle.portal.provider.v2.security.AccessControlException;
import oracle.portal.provider.v2.security.AuthenticationException;
import oracle.portal.provider.v2.preference.PreferenceStore;
import oracle.portal.provider.v2.preference.PreferenceStoreException;
import oracle.portal.provider.v2.monitor.PerfMonitor;
import oracle.portal.provider.v2.oid.OidManager;

/**
 *  A <code>ProviderInstance</code> is the entity that carries out the
 *  responsibilities of a single 'instance' of a provider registered on a
 *  single Portal. Generally, a <code>ProviderInstance</code> is driven by meta
 *  data information in a {@link ProviderDefinition} that may be shared with
 *  other <code>ProviderInstance</code>s registered on different portals.<p>
 */
public class ProviderInstance
{
   /**
     *  The constant that is used as the key for retrieving the {@link
     *  ProviderUser} from the {@link ProviderSession}.  This is provided as an
     *  alternative way to reference the current user.
     */
    public static final String PROVIDER_USER = "oracle.portal.provider.user";

    protected ProviderDefinition mProviderDefinition;
    protected String mProviderId;

    /**
     *  Initializes the provider instance after construction.
     *
     *  @param providerId the provider's ID, assigned by Portal.
     *  @param definition provider definition object holding meta data for this
     *  provider.
     */
    public void init(String providerId, ProviderDefinition definition) 
        throws ProviderException
    {
        mProviderId = providerId;
        mProviderDefinition = definition;
    }


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

    /**
     * Gets the context object that describes the deployment environment of the
     * provider. Generally, all providers running in the same environment share
     * the same <code>ProviderContext</code>.
     * @return context object describing the deployment environment of the
     * provider.
     */
    public ProviderContext getProviderContext()
    {
        return mProviderDefinition.getProviderContext();
    }

    /**
     * Signals whether {@link
     * oracle.portal.provider.v2.render.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 <code>PortletRendererUtil</code>.<code>true</code>
     * - render portal 3.0.6 style headers and footers. <code>false</code> -
     * render new style headers and footers.
     *
     * @deprecated use {@link ProviderDefinition#getContainerRenderer} to
     * determine the default container renderer that should be used to render
     * portlet containers for this provider
     */
    public boolean getUseOldStyleHeaders()
    {
        return mProviderDefinition.getUseOldStyleHeaders();
    }

    /**
     *  Returns the subscription information for this portal.  In a
     *  subscription-based model a company may want/need to associate its own
     *  ID or key with a customer/portal. This key can then be used to identify
     *  the each subscriber as requests are received. This information can be
     *  used to bill subscribers for using the provider.<p>
     * 
     *  For example, when a <code>ProviderInstance</code> is registered in a
     *  Portal, the subscription key is entered by the portal administrator and
     *  passed to the <code>ProviderInstance</code> in the
     *  <code>register</code> method.<p>
     * 
     *  Any persistent mapping that is required between the subscription key
     *  and any other provider specific information (eg an account number) must
     *  be established and maintained by extending
     *  <code>ProviderInstance</code>.
     *
     *  @return the subscription, or <code>null</code> if this
     *  <code>ProviderInstance</code> doesn't use / support subscription.
     */
    public ProviderSubscription getSubscription()
    {
        return null;
    }

    /**
     *  Register this newly created provider instance.  Having your register
     *  method called means your Provider has just been registered in a
     *  portal. This instance's provider ID will already have been set via a
     *  call to {@link #init}.<p>
     * 
     *  In response to this method call, the <code>ProviderInstance</code>
     *  enumerates its <code>PortletDefinition</code>s and calls {@link
     *  PortletDefinition#register} on each of them. This gives the
     *  <code>PortletDefinition</code> the opportunity to do some one time
     *  initialization for this <code>ProviderInstance</code>.
     * 
     *  @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 an object containing the dynamically generated subscription
     *  information and/or encryption key.  The properties on this object will
     *  override the defaults specified by the portal administrator.
     */
    public ProviderRegistrationInfo register(String subscriptionId,
                                             String portalVersion)
        throws ProviderException, IOException, AccessControlException
    {
        // create the portlet repository
        Iterator portletDefinitionIter = 
            mProviderDefinition.getPortletDefinitions().iterator();
        while(portletDefinitionIter.hasNext())
        {
            PortletDefinition portletDefinition = 
                (PortletDefinition)portletDefinitionIter.next();
            portletDefinition.register(this);
        }
        return null;
    }

    /**
     *  Deregister this provider instance.  This method is called when the
     *  provider is deregistered or removed from a portal.<p>
     *
     *  In response to this method call, the <code>ProviderInstance</code>
     *  enumerates its <code>PortletDefinition</code>s and calls {@link
     *  PortletDefinition#deregister} on each of them. This gives the
     *  <code>PortletDefinition</code> the opportunity to destroy or remove any
     *  persistent information relating to this ProviderInstance.  This
     *  includes personalizations, subscription information and preferences.
     */
    public void deregister() throws ProviderException
    {
        // remove the portlet repository

        Exception lastFail = null;
        Iterator portletDefinitionIter = 
            mProviderDefinition.getPortletDefinitions().iterator();
        while(portletDefinitionIter.hasNext())
        {
            PortletDefinition portletDefinition = 
                (PortletDefinition)portletDefinitionIter.next();
            try
            {
                portletDefinition.deregister(this);
            }
            catch (Exception e)
            {
                // log this error and then stash the Exception -- so we can
                // continue with the others
                LogManager.log("Failed to destroy portlet personalization "
                                + "repository.", e);
                lastFail = e;
            }
        }

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

    /**
     *  Called to initiate a session with a particular user. 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 {@link ProviderUser} user
     *  object. I.e. the user object contains the methods for creating and
     *  retrieving a session. For providers running in a servlet environment,
     *  further HTTP-specific information can be accessed by casting the user
     *  object to a {@link
     *  oracle.portal.provider.v2.http.ServletProviderUser}. In particular,
     *  this allows access to any existing Cookies already established between
     *  the user and provider, via the {@link
     *  oracle.portal.provider.v2.http.ServletProviderUser#getCookies()}
     *  method.<p>
     *
     *  @param user Current portal user.
     *  @param externalApp External Application information (if it exists).
     *  @return Array of cookie objects. In a servlet environment, these should
     *  be of class <code>javax.servlet.http.Cookie</code>. Alternatively,
     *  <b>null</b> can be returned if this provider doesn't use cookies or
     *  relies on the (user) session to maintain its state.
     *  @exception ProviderException if a generic provider error occurs.
     *  @exception 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, AuthenticationException
    {
        if (mProviderDefinition.getHasSession())
        {
            // Create the session if it doesn't already exist.
            ProviderSession session = user.getSession(true);
        }
        return null;
    }

    /**
     * Returns array of {@link PortletDefinition} objects for the portlets
     * managed by this Provider.  The range of definitions being requested is
     * controlled by <code>start</code> and <code>count</code>. This method
     * should return only <code>count</code> <code>PortletDefinitions</code>
     * starting at <code>start</code>.  If there are fewer then
     * <code>count</code> definitions then a shorter array should be
     * returned.<p>
     *
     * The <code>PortletDefinition</code>s returned by this method are
     * constrained to those the user is authorized to access if
     * <code>byUser</code> is true.<p>
     *
     * @param start The ordinal of the first portlet definition that should be
     * returned.
     * @param count The maximum number of portlet definitions that should be
     * returned.
     * @param byUser If true, indicates the returned list is constrained to 
     * those portlet definitions the user is authorized to access.
     * @param user The current user on whose behalf the request is being made.
     * @return array of portlet definitions constrained to the indicated range.
     */
    public PortletDefinition[] getPortletDefinitions(int start, 
                                                     int count, 
                                                     boolean byUser, 
                                                     ProviderUser user)
        throws ProviderException
    {
        List portletDefinitions = mProviderDefinition.getPortletDefinitions();
        int size = Math.min(count, portletDefinitions.size());
        PortletDefinition[] p = new PortletDefinition[size];
        int elementsCopied = 0;
        int s = 0;
        Iterator i = portletDefinitions.iterator();
        while(i.hasNext() && elementsCopied < size)
        {
            PortletDefinition portletDefinition = (PortletDefinition)i.next();
            if (byUser && !portletDefinition.hasAccess(this, user))
            {
                continue;
            }

            if (s < start)
            {
                s++;
                continue;
            }
            p[elementsCopied++] = portletDefinition;
        }

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

    /**
     * Returns an array of {@link PortletDefinition} objects for all of the 
     * portlets managed by this Provider.  
     *
     * The <code>PortletDefinition</code>s returned by this method are
     * constrained to those the user is authorized to access if
     * <code>byUser</code> is true.<p>
     *
     * @param byUser If true, indicates the returned list is constrained to 
     * those portlet definitions the user is authorized to access.
     * @param user The current user on whose behalf the request is being made.
     * @return array of portlet definitions constrained to the indicated range.
     */
    public PortletDefinition[] getPortletDefinitions(boolean byUser,
                                                     ProviderUser user)
    throws ProviderException
    {
        int numPortlets = mProviderDefinition.getPortletDefinitions().size();
        return getPortletDefinitions(0, numPortlets, byUser, user);
    }

    /**
     *  Returns the <code>PortletDefinition</code> with the given ID.  The
     *  <code>PortletDefinition</code> should only be returned if the user is
     *  authorized to access it.<p>
     *
     *  @param portletId  portlet Id.
     *  @param user Current user.
     *  @return the <code>PortletDefinition</code> associated with the given
     *  Id, or <code>null</code> if the user isn't authorized to access the
     *  portlet.
     *  @exception ProviderException if no <code>PortletDefinition</code> with
     *  the given id exists
     */
    public PortletDefinition getPortletDefinition(long portletId,
                                                  ProviderUser user)
        throws ProviderException
    {
        return getPortletDefinition(portletId, user, true);
    }

    /**
     *  Returns the <code>PortletDefinition</code> with the given ID.  The
     *  <code>PortletDefinition</code> should only be returned if the user is
     *  authorized to access it.<p>
     *
     *  @param portletId  portlet Id.
     *  @param user Current user.
     *  @param securityCheck controls whether or not an access check is
     *  performed.
     *  @return the <code>PortletDefinition</code> associated with the given
     *  Id. If the <code>securityCheck</code> parameter is <code>true</code>,
     *  and the user isn't authorized to access the portlet, <code>null</code>
     *  will be returned.
     *  @exception ProviderException if no <code>PortletDefinition</code> with
     *  the given id exists
     */
    public PortletDefinition getPortletDefinition(long portletId, 
                                                  ProviderUser user,
                                                  boolean securityCheck) 
        throws ProviderException
    {
        PortletDefinition pd = 
            mProviderDefinition.getPortletDefinition(portletId);
        if(pd == null)
        {
            throw new ProviderException("PortletDefinition with id " + 
                                        portletId + " doesn't exist");
        }

        // If we are required to, do the access check returning null if 
        // denied.
        if(securityCheck && !pd.hasAccess(this, user))
        {
            return null;
        }

        // Otherwise, just return the definition.
        return pd;
    }

    /**
     * Constructs a <code>PortletInstance</code> object based on the specified
     * information. <code>PortletInstance</code> implements the behaviour of a
     * specific instance of a portlet on a portal page.<p>
     *
     * This method uses the default locale specified on the
     * <code>PortletDefinition</code> as the instance's default locale. Use
     * {@link #getPortletInstance(long, String, ProviderUser, Locale)} if the
     * calling Portal has passed a default locale.
     *
     * @param portletId id of the portlet
     * @param portletInstanceName instance name of the portlet on the portal
     * page
     * @param user the user who is accessing the portlet
     *
     * @return a new <code>PortletInstance</code> object based on the
     * parameters passed
     *
     * @deprecated Use {@link #getPortletInstance(long, String, ProviderUser, 
     *             PerfMonitor)} instead.
     */
    public PortletInstance getPortletInstance(long portletId, 
                                              String portletInstanceName, 
                                              ProviderUser user)
        throws ProviderException, PortletException
    {
        return getPortletInstance(portletId, portletInstanceName, user, null, 
                                  null);
    }
        
    /**
     * Constructs a <code>PortletInstance</code> object based on the specified
     * information. <code>PortletInstance</code> implements the behaviour of a
     * specific instance of a portlet on a portal page.
     *
     * @param portletId id of the portlet
     * @param portletInstanceName instance name of the portlet on the portal
     * page
     * @param user the user who is accessing the portlet
     * @param defaultLocale the instance's default locale, or <code>null</code>
     * if the <code>PortletDefinition</code>'s default locale is to be used.
     *
     * @return a new <code>PortletInstance</code> object based on the
     * parameters passed
     *
     * @deprecated Use {@link #getPortletInstance(long, String, ProviderUser, 
     *             Locale, PerfMonitor)} instead.
     */
    public PortletInstance getPortletInstance(long portletId, 
                                              String portletInstanceName, 
                                              ProviderUser user,
                                              Locale defaultLocale)
        throws ProviderException, PortletException
    {
        return getPortletInstance(portletId, portletInstanceName, user, 
                                  defaultLocale, null);        
    }

    /**
     * Constructs a <code>PortletInstance</code> object based on the specified
     * information. <code>PortletInstance</code> implements the behaviour of a
     * specific instance of a portlet on a portal page.
     *
     * @param portletId id of the portlet
     * @param portletInstanceName instance name of the portlet on the portal
     * page
     * @param user the user who is accessing the portlet
     * @param tracker the tracking object used to check the request execution 
     * time against the warning timeout specified in the provider properties.
     *
     * @return a new <code>PortletInstance</code> object based on the
     * parameters passed
     */
    public PortletInstance getPortletInstance(long portletId, 
                                              String portletInstanceName, 
                                              ProviderUser user,
                                              PerfMonitor monitor)
        throws ProviderException, PortletException
    {
        return getPortletInstance(portletId, portletInstanceName, user, 
                                  null, monitor);        
    }

    /**
     * Constructs a <code>PortletInstance</code> object based on the specified
     * information. <code>PortletInstance</code> implements the behaviour of a
     * specific instance of a portlet on a portal page.
     *
     * @param portletId id of the portlet
     * @param portletInstanceName instance name of the portlet on the portal
     * page
     * @param user the user who is accessing the portlet
     * @param defaultLocale the instance's default locale, or <code>null</code>
     * if the <code>PortletDefinition</code>'s default locale is to be used.
     * @param monitor the tracking object used to check the request execution 
     * time against the warning timeout specified in the provider properties.
     *
     * @return a new <code>PortletInstance</code> object based on the
     * parameters passed
     */
    public PortletInstance getPortletInstance(long portletId,
                                              String portletInstanceName,
                                              ProviderUser user,
                                              Locale defaultLocale,
                                              PerfMonitor monitor)
        throws ProviderException, PortletException
    {
        return getPortletInstance(portletId, portletInstanceName, user,
                                  defaultLocale, monitor, true);
    }

    /**
     * Constructs a <code>PortletInstance</code> object based on the specified
     * information. <code>PortletInstance</code> implements the behaviour of a
     * specific instance of a portlet on a portal page.
     *
     * @param portletId id of the portlet
     * @param portletInstanceName instance name of the portlet on the portal
     * page
     * @param user the user who is accessing the portlet
     * @param defaultLocale the instance's default locale, or <code>null</code>
     * if the <code>PortletDefinition</code>'s default locale is to be used.
     * @param monitor the tracking object used to check the request execution 
     * time against the warning timeout specified in the provider properties.
     * @param securityCheck controls whether or not the
     * {@link oracle.portal.provider.v2.security.PortletSecurityManager} is
     * queried for the user's access rights.
     *
     * @return a new <code>PortletInstance</code> object based on the
     * parameters passed
     */
    public PortletInstance getPortletInstance(long portletId, 
                                              String portletInstanceName, 
                                              ProviderUser user,
                                              Locale defaultLocale,
                                              PerfMonitor monitor,
                                              boolean securityCheck)
        throws ProviderException, PortletException
    {
        // First, get the corresponding PortletDefinition, and ensure the user
        // has the correct access rights to see it
        PortletDefinition pd = getPortletDefinition(portletId, user,
                                                    securityCheck);
        if(pd == null)
        {
            return null;
        }
        else
        {
            PortletInstance pi;
            try
            {
                pi = (PortletInstance)pd.getPortletInstanceClass().newInstance();
            }
            catch(Exception e)
            {
                throw new PortletException(e);
            }

            // Initialize the instance.
            pi.init(this, pd, portletInstanceName, defaultLocale, monitor);

            // If required, query the security manager, returning null if
            // access is denied.
            if (securityCheck && !pi.hasAccess(user, defaultLocale))
            {
                return null;
            }

            // Otherwise, return the instance.
            return pi;
        }
    }

    /**
     * Gets the default <code>PreferenceStore</code> that should be used by the
     * <code>ProviderInstance</code> and all its child objects for which a
     * preference store has not been chosen explicitly.
     * @return the default preference store for this <code>ProviderInstance</code>
     * @exception PreferenceStoreException if the <code>ProviderInstance</code>
     * does not have a default preference store.
     */
    public PreferenceStore getPreferenceStore()
        throws PreferenceStoreException
    {
        PreferenceStore ps =  mProviderDefinition.getPreferenceStore();
        if(ps == null)
        {
            throw new PreferenceStoreException("No preference store defined");
        }
        return ps;
    }
    
    
    /**
     * Compare the internal <code>ProviderDefinition</code> with that
     * specified.<p>
     *
     * The {@link ProviderLoader} needs to be able to compare the internal
     * definition of a <code>ProviderInstance</code> with it's own definition
     * to determine if that instance needs to be replaced by a newer one.
     * 
     * @param pd <code>ProviderDefinition</code> to compare to internal
     * definition
     *
     * @return <code>true</code> if the <code>ProviderDefinition</code>s are
     * the same, otherwise <code>false</code>.
     */
    public boolean isDefinitionEqual(ProviderDefinition pd)
    {
        return (pd == mProviderDefinition);
    }
    
    /**
     * Gets the OID Manager that provides access to the OID repository.
     * The OID manager can be used to get access to user details.
     * @return OidManager or null depending upon whether the OidManager 
     * has been configured.
     */
    public OidManager getOidManager() throws ProviderException
    {
        return mProviderDefinition.getOidManager();
    }

}
