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

package oracle.portal.provider.v2;

import java.io.IOException;
import java.util.Locale;
import oracle.portal.provider.v2.render.PortletRenderer;
import oracle.portal.provider.v2.render.PortletRenderRequest;
import oracle.portal.provider.v2.render.RenderContext;
import oracle.portal.provider.v2.render.http.ServletPortletRenderRequest;
import oracle.portal.provider.v2.personalize.PortletPersonalizationManager;
import oracle.portal.provider.v2.personalize.PersonalizationObject;
import oracle.portal.provider.v2.personalize.PortletReference;
import oracle.portal.provider.v2.security.PortletSecurityManager;
import oracle.portal.provider.v2.security.AccessControlException;
import oracle.webdb.utils.SimpleStringBuffer;
import oracle.portal.log.LogManager;
import oracle.portal.provider.v2.monitor.PerfMonitor;

/**
 *  <code>DefaultPortletInstance</code> is the default implementation of a
 *  {@link PortletInstance}, which carries out the responsibilities of a single
 *  'instance' of a portlet on a single Portal page. A
 *  <code>DefaultPortletInstance</code> is driven by meta data information in a
 *  {@link DefaultPortletDefinition} and thus has the various portlet
 *  'controller objects' at its disposal.<p>
 */
public class DefaultPortletInstance extends PortletInstance
{
    private DefaultPortletDefinition mPortletDefinition;
    private PersonalizationObject mEditData = null;
    private PersonalizationObject mEditDefaultData = null;

    /**
     *  Initialize the portlet instance after construction.
     *
     * @param providerInstance Provider of this instance
     * @param portletDefinition definition of this instance. This must be a
     * {@link DefaultPortletDefinition}.
     * @param name Portal generated name of this instance
     * @param defLocale the default language/locale setting of the portal, if
     * available, or <code>null</code> if the default locale defined on the
     * <code>PortletDefinition</code> should be used.
     * @exception PortletException if an error occurs during initialization.
     *
     * @deprecated Replaced by {@link #init(ProviderInstance, 
     *             PortletDefinition, String, Locale, PerfMonitor)}
     */
    public void init(ProviderInstance providerInstance,
                     PortletDefinition portletDefinition,
                     String name, Locale defLocale)
        throws PortletException
    {
        if(!(portletDefinition instanceof DefaultPortletDefinition))
        {
            throw new PortletException("DefaultPortletInstance must be an instance of a DefaultPortletDefinition");
        }
        super.init(providerInstance, portletDefinition, name, defLocale);
        mPortletDefinition = (DefaultPortletDefinition)portletDefinition;
    }

    /**
     *  Initialize the portlet instance after construction.
     *
     * @param providerInstance Provider of this instance
     * @param portletDefinition definition of this instance. This must be a
     * {@link DefaultPortletDefinition}.
     * @param name Portal generated name of this instance
     * @param defLocale the default language/locale setting of the portal, if
     * available, or <code>null</code> if the default locale defined on the
     * <code>PortletDefinition</code> should be used.
     * @param perfMonitor a performance monitor object used for tracking request progress 
     * @exception PortletException if an error occurs during initialization.
     */
    public void init(ProviderInstance providerInstance,
                     PortletDefinition portletDefinition,
                     String name, 
                     Locale defLocale,
                     PerfMonitor perfMonitor)
        throws PortletException
    {
        if (!(portletDefinition instanceof DefaultPortletDefinition))
        {
            throw new PortletException("DefaultPortletInstance must be an instance of a DefaultPortletDefinition");
        }
        super.init(providerInstance, portletDefinition, name, defLocale, perfMonitor);
        mPortletDefinition = (DefaultPortletDefinition)portletDefinition;
    }


    /**
     * Called when the portlet instance is first created on a page. This gives
     * the <code>DefaultPortletInstance</code> the opportunity to ask its
     * {@link PortletPersonalizationManager} to initialize defaults for
     * persisted customization data.
     * @param user the user performing this operation.
     * @exception IOException if an I/O error occurs whilst accessing
     * persistent storage.
     * @exception PortletAlreadyExistsException if this instance has already
     * been registered.
     * @exception AccessControlException if the given user isn't authorized to
     * access this instance.
     */
    public void register(ProviderUser user)
        throws IOException, PortletAlreadyExistsException, AccessControlException
    {
        // Create 'default' PortletReference
        PortletReference ref = new PortletReference(this, null, null);

        PortletPersonalizationManager pMgr = mPortletDefinition.getPersonalizationManager();
        if (pMgr != null)
        {
            pMgr.create(ref, user);
        }
    }

    /**
     * Called when the portlet instance is removed from the page. This gives
     * the <code>DefaultPortletInstance</code> the opportunity to ask its
     * {@link PortletPersonalizationManager} to remove any persisted
     * customization data.
     * @param user the user performing this operation.
     * @exception IOException if an I/O error occurs whilst accessing
     * persistent storage.
     * @exception PortletNotFoundException if this instance isn't known to
     * exist in the Portal, i.e. hasn't been registered.
     * @exception AccessControlException if the given user isn't authorized to
     * access this instance.
     */
    public void deregister(ProviderUser user)
        throws IOException, PortletNotFoundException, AccessControlException
    {
        // Create 'default' PortletReference
        PortletReference ref = new PortletReference(this, null, null);

        PortletPersonalizationManager pMgr = mPortletDefinition.getPersonalizationManager();
        if (pMgr != null)
        {
            pMgr.destroy(ref, user);
        }
    }

    /**
     * Called when the portlet instance is copied to another page. This gives
     * the <code>DefaultPortletInstance</code> the opportunity to ask its
     * {@link PortletPersonalizationManager} to export defaults for persisted
     * customization data to the new instance.
     * @param user the user performing this operation.
     * @param dest the <code>PortletInstance</code> to export data to.
     * @exception IOException if an I/O error occurs whilst accessing
     * persistent storage.
     * @exception PortletNotFoundException if this instance or the destination
     * instance isn't known to exist in the Portal, i.e. hasn't been
     * registered.
     * @exception AccessControlException if the given user isn't authorized to
     * access this instance or the destination instance.
     */
    public void copyTo(ProviderUser user, PortletInstance dest)
        throws IOException, PortletNotFoundException, AccessControlException
    {
        // Create 'default' PortletReferences
        PortletReference ref = new PortletReference(this, null, null);
        PortletReference destRef = new PortletReference(dest, null, null);

        PortletPersonalizationManager pMgr = mPortletDefinition.getPersonalizationManager();
        if (pMgr != null)
        {
            pMgr.copy(ref, destRef, user);
        }
    }

    /**
     *  Verifies whether the user is authorized to use this particular portlet
     *  instance. This implementation uses the portlet's {@link
     *  PortletSecurityManager} to verify access, if one is defined, or returns
     *  <code>true</code> otherwise.
     *
     *  @param user the current user we are being asked to authorize.
     *  @param locale the language setting with which the user has logged on to
     *  portal.
     *  @return <code>true</code> if the user is authorized to access this
     *  portlet instance.
     *  @exception PortletNotFoundException if this instance isn't known to
     *  exist in the Portal, i.e. hasn't been registered.
     */
    public boolean hasAccess(ProviderUser user, Locale locale)
        throws PortletNotFoundException
    {
        // Check access rights on a 'default' reference to this instance
        PortletReference ref = new PortletReference(this, null, locale);
        PortletSecurityManager securityMgr = mPortletDefinition.getSecurityManager();
        if (securityMgr != null)
        {
            return securityMgr.hasAccess(ref, user);
        }
        return true;
    }

    /**
     * Retrieves an object containing the customizable data for the given user
     * in the given locale within the given context. If the context is "Edit
     * Defaults" mode, the data should correspond to the defaults for that
     * locale, rather than the data for that specific user.<p>
     *
     * This implementation first checks whether any customization data has been
     * cached for this portlet instance during this request. If so, it returns
     * it. Otherwise, if a {@link PortletPersonalizationManager} is defined for
     * the portlet, it is used to retrieve a {@link PersonalizationObject},
     * which is returned and cached for the duration of this
     * request. Otherwise, <code>null</code> is returned.
     *
     * @param user the user for whom the data is being retrieved.
     * @param locale the language setting with which the user has logged on to
     * portal.
     * @param rc an object describing the context in which this
     * <code>PortletInstance</code> is being rendered.
     * @return a {@link PersonalizationObject} containing the customizable data
     * for the given user in the given locale within the given context, or
     * <code>null</code> if none exists.
     * @exception IOException if an I/O error occurs whilst retrieving the data
     * @exception PortletNotFoundException if this instance isn't known to
     * exist in the Portal, i.e. hasn't been registered.
     * @exception AccessControlException if the given user isn't authorized to
     * access this instance.
     */
    public Object getEditData(ProviderUser user, 
                              Locale locale, 
                              RenderContext rc)
        throws IOException, 
               PortletNotFoundException, 
               AccessControlException
    {
        PerfMonitor monitor = rc.getPerfMonitor();

        // Start the timer to record time taken to read the data.
        monitor.startTimer(PerfMonitor.READ_CUSTOMIZATION_ACCR);

        // if we've already read the edit data in the processing of this request
        // return the cached data
        PersonalizationObject po = getCachedEditData();
        if (po == null)
        {
            PortletReference ref = getPortletReference(user, locale, rc);
            PortletPersonalizationManager pMgr = mPortletDefinition.getPersonalizationManager();
            if (pMgr != null)
            {
                // Note that PortletPersonalizationManager.read() now
                // automatically 'inherits' data from defaults
                po = pMgr.read(ref, user);

                // cache the edit data for future use in this request
                setCachedEditData(po);
            }
        }

        // Stop the timer.
        monitor.finishTimer(PerfMonitor.READ_CUSTOMIZATION_ACCR);

        return po;
    }

    /**
     * Retrieves an object containing the default data for the
     * <code>PortletInstance</code> in the given locale.
     *
     * If any default data has been previously cached for this portlet instance
     * during this request then it is returned.
     * Otherwise, if a {@link PortletPersonalizationManager} is defined for
     * the portlet, it is used to retrieve a {@link PersonalizationObject},
     * which is returned and cached for the duration of this
     * request. Otherwise, <code>null</code> is returned.
     *
     * @param locale the language setting with which the user
     *        has logged on to Portal.
     * @param rc an object describing the context in which this
     *        <code>PortletInstance</code> is being rendered.
     * @return an object containing the default data for the
     *         <code>PortletInstance</code> in the given locale within the
     *         given context, or <code>null</code> if none
     *         exists.
     * @exception IOException if an I/O error occurs whilst retrieving the data
     * @exception PortletNotFoundException if this instance isn't known to
     *            exist in the Portal, i.e. hasn't been registered.
     * @exception AccessControlException if the given user isn't authorized to
     *            access this instance.
     */
    public Object getEditDefaultData(Locale locale,
                                     RenderContext rc)
        throws IOException, PortletNotFoundException, AccessControlException
    {
        PerfMonitor monitor = rc.getPerfMonitor();

        // Start the timer to record time taken to read the data.
        monitor.startTimer(PerfMonitor.READ_CUSTOMIZATION_ACCR);

        // if we've already read the edit data in the processing of this request
        // return the cached data
        PersonalizationObject po = getCachedEditDefaultData();
        if (po == null)
        {
            PortletReference ref = new PortletReference(this, null, locale);
            PortletPersonalizationManager pMgr = 
                mPortletDefinition.getPersonalizationManager();
            if (pMgr != null)
            {
                // Note that PortletPersonalizationManager.read() now
                // automatically 'inherits' data from defaults
                po = pMgr.read(ref, null);

                // cache the edit data for future use in this request
                setCachedEditDefaultData(po);
            }
        }

        // Stop the timer.
        monitor.finishTimer(PerfMonitor.READ_CUSTOMIZATION_ACCR);

        return po;
    }

    /**
     * Stores an object containing the customizable data for the given user in
     * the given locale within the given context. If the context is "Edit
     * Defaults" mode, the data should be stored as the defaults for that
     * locale, rather than the data for that specific user.<p>
     *
     * This implementation caches the given object for the duration of this
     * request, and if a {@link PortletPersonalizationManager} is defined for
     * the portlet, it is used to persist the data so that it is available for
     * subsequent requests.
     *
     * @param user the user for whom the data is being stored.
     * @param locale the language setting with which the user has logged on to
     * portal.
     * @param rc an object describing the context in which this
     * <code>PortletInstance</code> is being rendered.
     * @param o an object containing the customizable data for the given user
     * in the given locale within the given context. Should be a {@link
     * PersonalizationObject}.
     * @exception IOException if an I/O error occurs whilst retrieving the data
     * @exception AccessControlException if the given user isn't authorized to
     * access this instance.
     */
    public void submitEditData(ProviderUser user, 
                               Locale locale, 
                               RenderContext rc, 
                               Object o)
        throws IOException, AccessControlException
    {
        PerfMonitor monitor = rc.getPerfMonitor();

        // Start the timer to record time taken to read the data.
        monitor.startTimer(PerfMonitor.WRITE_CUSTOMIZATION_ACCR);

        PortletReference ref = getPortletReference(user, locale, rc);
        PortletPersonalizationManager pMgr = 
            mPortletDefinition.getPersonalizationManager();
        PersonalizationObject po = null;

        // Personalization Framework requires data objects to be of type
        // 
        try
        {
            po = (PersonalizationObject)o;
        }
        catch (ClassCastException e)
        {
            SimpleStringBuffer msg = new SimpleStringBuffer(100);
            msg.append("DefaultPortletInstance.submitEditData() - failed to ");
            msg.append("cast data object of type: ");
            msg.append(o.getClass().getName());
            msg.append(" to PersonalizationObject");

            LogManager.severe(msg.toString(), e);
            throw new IOException(msg.toString());
        }
        
        
        if (pMgr != null)
        {
            try
            {
                
                pMgr.write(ref, po, user);
            }
            catch (PortletNotFoundException pnfe)
            {
                // create the customization
                try
                {
                    pMgr.create(ref, po, user);
                }
                catch (PortletAlreadyExistsException paee)
                {
                    // did we have a race condition
                    try
                    {
                        pMgr.write(ref, po, user);
                    }
                    catch (PortletNotFoundException apnfe)
                    {
                        // should never get here
                    }
                }
            }
        }

        // cache the modified edit data
        setCachedEditData(po);

        // Stop the timer.
        monitor.finishTimer(PerfMonitor.WRITE_CUSTOMIZATION_ACCR);
    }

    /**
     * Renders a visual representation of this portlet instance for the given
     * user in the given locale in the given context. This implementation uses
     * the portlet's {@link PortletRenderer} to perform the rendering.
     *
     * @param user the user for whom this <code>PortletInstance</code> is being
     * rendered.
     * @param locale the language setting with which the user has logged on to
     * portal.
     * @param rc an object describing the context in which this
     * <code>PortletInstance</code> is being rendered.
     * @exception AccessControlException if the given user isn't authorized to
     * access this instance.
     * @exception PortletException if a general portlet exception occurs whilst
     * rendering.
     */ 
    public void render(ProviderUser user, Locale locale, RenderContext rc)
        throws AccessControlException, PortletException
    {
        PortletReference ref = getPortletReference(user, locale, rc);
        PortletRenderRequest pr =
            new ServletPortletRenderRequest(ref, user, rc);
        mPortletDefinition.getRenderer().render(pr);
    }

    /**
     * Gets a reference to this portlet instance applicable to the given locale
     * and context. If the context is "Edit Defaults" mode, the default user
     * rather than the given logged in user should be referenced.
     * @param user the user for whom the reference is being created.
     * @param locale the language setting with which the user has logged on to
     * portal.
     * @param rc an object describing the context in which this
     * <code>PortletInstance</code> is being rendered.
     * @return a reference to this portlet instance applicable to the given
     * locale and context.
     */
    protected PortletReference getPortletReference(ProviderUser user, Locale locale, RenderContext rc)
    {
        PortletReference ref;
        if(rc.getMode() == PortletRenderer.MODE_EDIT_DEFAULTS)
        {
            ref = new PortletReference(this, null, locale);
        }
        else
        {
            ref = new PortletReference(this, user, locale);
        }
        return ref;
    }


    /**
     * Caches the edit data for this <code>PortletInstance</code>.<p>
     *
     * The edit or customization data can be cached in the
     * <code>PortletInstance</code> because the <code>PortletInstance</code>
     * only exists for the life of a single request.
     * 
     * @param po the <code>PersonalizationObject</code> containing the
     * customizations for this PortletInstance/User
     */
    protected void setCachedEditData(PersonalizationObject po)
    {
        mEditData = po;
    }
    

    /**
     * Get the cached edit data.
     *
     * The edit or customization data can be cached in the
     * <code>PortletInstance</code> because the <code>PortletInstance</code>
     * only exists for the life of a single request.
     * 
     * @return the cached edit data for this PortletInstance or NULL if the
     * edit data has not been read yet
     */
    protected PersonalizationObject getCachedEditData()
    {
        return mEditData;
    }

    /**
     * Caches the edit defaults data for this <code>PortletInstance</code>.<p>
     *
     * The edit default data can be cached in the
     * <code>PortletInstance</code> because the <code>PortletInstance</code>
     * only exists for the life of a single request.
     * 
     * @param po the <code>PersonalizationObject</code> containing the
     * edit default data for this PortletInstance
     */
    protected void setCachedEditDefaultData(PersonalizationObject po)
    {
        mEditDefaultData = po;
    }
    

    /**
     * Get the cached edit default data.
     *
     * The edit default data can be cached in the
     * <code>PortletInstance</code> because the <code>PortletInstance</code>
     * only exists for the life of a single request.
     * 
     * @return the cached edit default data for this PortletInstance or 
     * NULL if the edit data has not been read yet
     */
    protected PersonalizationObject getCachedEditDefaultData()
    {
        return mEditDefaultData;
    }
}
