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

package oracle.portal.provider.v2.personalize;

import oracle.portal.provider.v2.ProviderInstance;
import oracle.portal.provider.v2.PortletDefinition;
import oracle.portal.provider.v2.PortletInstance;
import oracle.portal.provider.v2.ProviderUser;
import oracle.portal.provider.v2.PortletAlreadyExistsException;
import oracle.portal.provider.v2.PortletNotFoundException;
import oracle.portal.provider.v2.Validateable;
import oracle.portal.provider.v2.security.AccessControlException;
import oracle.portal.provider.v2.utils.ValidationError;
import oracle.webdb.utils.SimpleStringBuffer;
import oracle.portal.log.LogManager;
import oracle.portal.provider.v2.PortletNotFoundException;

import java.io.IOException;
import java.util.HashMap;

/**
 *  The PortletPersonalizationManager manages customization data for a portlet.  
 * 
 *  Each customization must be created before it can be read/written.  The
 *  {@link #create(PortletReference, ProviderUser)} method establishes a new 
 *  entry and populates it with default values. An alternative 
 *  {@link #create(PortletReference, PersonalizationObject, ProviderUser)} 
 *  method is defined to allow clients to create a new instance with specific 
 *  values.<p>
 * 
 *  Once created, customization attributes can be read from the persistent store
 *  using the {@link #read(PortletReference, ProviderUser)} method, modified by 
 *  updating the attributes of the {@link PersonalizationObject} and saved again 
 *  using the {@link #write(PortletReference, PersonalizationObject, 
 *  ProviderUser)} method.<p>
 *
 *  A customization is identified by its {@link PortletReference}. This 
 *  reference may refer to a specific user instance or the default instance 
 *  (see {@link PortletReference} for details). Unfortunately, the portal 
 *  doesn't have knowledge of a specific user instance.  It merely understands 
 *  that a (single) portlet instance exists on a given page.  The portal 
 *  therefore leaves this determination up to the provider.  
 *
 *  Except when the operation is explicitly constrained to the default
 *  portlet instance, the JPDK always generates the deepest PortletReference.
 *  I.e. a reference to the per user customization.  When reading a customization
 *  the read() method should start with the most specific reference specified
 *  by the PortletReference and work back towards the least specific until it
 *  finds a match for the portlet instance. 
 * 
 *  If a match is not found at all, the Personalization Manager can throw a
 *  PortletNotFoundException or create a default customization using the default
 *  values. Under normal circumstances, a call to read() should always locate at
 *  least the default customization. Failure to do so indicates that data has
 *  been lost or the portlet instance was not registered correctly when it was
 *  added to the page.
 */
public abstract class PortletPersonalizationManager implements Validateable
{

    /**
     * class that represents the data object that will be stored by this
     * personalization manager
     */
    private Class mDataClass;

    /**
     *  Sets the class name of the data objects this instance manages.  
     *
     *  This class must implement the {@link PersonalizationObject} interface.
     *  The framework provides an implementation of this interface calle
     *  {@link NameValuePersonalizationObject}.
     *
     *  @param className the class name for that class that represents the data
     *  to be managed.
     */
    public final void setDataClass(String className) 
        throws PersonalizationException
    {
        Object template = null;
        try
        {
            // We have to use the the current thread's 'context class loader'
            // (i.e. the web app's class loader) to load the class, so that it
            // can be picked up from within the web app
            mDataClass = Class.forName(className, true,
                                       Thread.currentThread().getContextClassLoader());
            template = mDataClass.newInstance();
        }
        catch (ClassNotFoundException e)
        {
          throw new PersonalizationException(e);
        }
        catch (IllegalAccessException e)
        {
          throw new PersonalizationException(e);
        }
        catch (InstantiationException e)
        {
          throw new PersonalizationException(e);
        }

        // Decide up front whether we are going to have to use Java
        // Serialization for the data objects, rather than the
        // PersonalizationObject interface, for backwards compatibility
        if(!(template instanceof PersonalizationObject))
        {
            SimpleStringBuffer msg = new SimpleStringBuffer(100);
            msg.append("PortletPersonalizationManager.setDataClass(): ");
            msg.append("Class ");
            msg.append(className);
            msg.append(" does not implement the PersonalizationObject interface");

            LogManager.severe(msg.toString());
            throw new PersonalizationException(msg.toString());
        }
    }
    

    /**
     *  Gets the class name of the data objects this instance manages.  
     *
     *  @return the name of the class
     */
    public final Class getDataClass() 
    {
        return mDataClass;
    }
   
    //
    // abstract methods start here...
    // 

    /**
     *  Called to allow this PortletPersonalizationManager to initialize its
     *  repository. This method is invoked on each of a Provider's
     *  PortletPersonalizationManagers whenever the Provider is registered on a
     *  Portal Node (when the Provider's <code>register()</code> method is
     *  called). This gives the personalization manager an opportunity to
     *  initialize the repository for the corresponding Portlet.<p>
     *
     *  @param  p the portlet this PortletPersonalizationManager is controlling.
     *  From the Portlet you can get the Provider to identify the specific instance
     *  being created.
     */
    public abstract void init(PortletDefinition p);

    /**
     *  Called to allow the Portlet Personalization manager to destroy its
     *  repository.  This method is called each time a Provider that manages
     *  this PortletPersonalizationManager's Portlet is deregistered (from a portal).
     *
     *  @param provider the provider instance that is being deregistered.
     *  @param p the portlet this PortletPersonalizationManager is controlling.
     *  From the Portlet you can get the Provider to identify the specific
     *  instance going away.
     */
    public abstract void destroy(ProviderInstance provider, PortletDefinition p)
        throws AccessControlException, IOException;

    /**
     * Creates the initial personalization reference for this (new)
     * PortletReference and establishes its (static) defaults.
     *
     * @param ref the PortletReference.  The details of the PortletReference
     * determine whether this creates the default, system instance or a specific
     * user instance.
     * @param user the user attempting this operation.  The manager is expected
     * to authorize the user before completing this operation.
     */
    public abstract void create(PortletReference ref, ProviderUser user)
        throws PortletAlreadyExistsException, AccessControlException, java.io.IOException;

    /**
     * Creates the initial personalization reference for this (new)
     * PortletReference. The initial setting are set from the object passed in .
     *
     * @param ref the PortletReference.  The details of the PortletReference
     * determine whether this creates the default, system instance or a specific
     * user instance.
     * @param o object containing the customizations.  It is expected that the
     * manager know how to access/control this object to maintain a persistent
     * store.
     * @param user the user attempting this operation.  The manager is expected
     * to authorize the user before completing this operation.
     */
    public abstract void create(PortletReference ref, 
                                PersonalizationObject o, 
                                ProviderUser user)
        throws PortletAlreadyExistsException, 
               AccessControlException, 
               IOException;

    /**
     *  Returns true is a customization exists for this given reference.  This
     *  will be true if a create() has been previously called.
     *  @param ref the portlet instance to check
     *  @user the user making this request -- passed to give the manager access
     *  the the session.
     */
    public abstract boolean exists(PortletReference ref, ProviderUser user)
        throws AccessControlException, IOException;


    /**
     *  Returns an object containing the customizations particular to this reference.
     *  If no customizations exist then the PortletNotFoundException is thrown.
     *  @param ref the portlet referecne that identifies the instance whose
     *  customizations are being requested.
     *  @param user the user attempting this operation.  The manager is expected
     * to authorize the user before completing this operation.
     */
    public abstract PersonalizationObject read(PortletReference ref, 
                                               ProviderUser user) 
        throws PortletNotFoundException, AccessControlException, IOException;

    /**
     *  Updates this references customizations.
     *  @param ref the portlet reference that identifies the instance whose
     *  customizations are being updated.
     *  @param o the object containing the new values.
     *  @param user the user attempting this operation.  The manager is expected
     * to authorize the user before completing this operation.
     */
    public abstract void write(PortletReference ref, 
                               PersonalizationObject o, 
                               ProviderUser user)
        throws IOException, 
               PortletNotFoundException, 
               AccessControlException;

    /**
     *  Destroys a given Portlet instance from the repository by reference. If
     *  the reference refers to the system (default) instance the manager
     *  should additionally delete all user customizations that correspond.
     *
     *  @param  ref the portlet reference that identifies the instance whose
     *  customizations are being deleted.
     *  @param user the user attempting this operation.  The manager is expected
     * to authorize the user before completing this operation.
     */
    public abstract void destroy(PortletReference ref, 
                                 ProviderUser user)
        throws PortletNotFoundException, 
               AccessControlException, 
               IOException;


    /**
     *  Copies the instance customizations from one reference to another.  If
     *  the customization for the to reference doesn't exist it is created else
     *  the current values are overwritten.
     *
     *  @param from reference for the portlet isntance we are copying from.
     *  @param to reference for the portlet isntance we are copying to.
     *  @param user the user attempting this operation.  The manager is expected
     * to authorize the user before completing this operation.
     */
    public abstract void copy(PortletReference from, 
                              PortletReference to, 
                              ProviderUser user)
        throws PortletNotFoundException, AccessControlException, IOException;

    /**
     * Validates the internal state of this
     * <code>PortletPersonalizationManager</code> instance. Errors found are
     * reported using the supplied {@link
     * oracle.portal.provider.v2.utils.ValidationError} instance.
     *
     * @param context The context of the calling instance.
     * @param errors The current list of errors.
     */
    public void validate(String context, ValidationError errors)
    {
        String ctx =
            ValidationError.newContext(context, "PortletPersonalizationManager",
                                       null);

        // Ensure the data class is set.
        if (mDataClass == null)
        {
            errors.addError(ctx, "data class not set", null);
        }
    }
    
}
