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

package oracle.portal.provider.v2;

import java.io.IOException;
import java.util.*;
import oracle.portal.provider.v2.render.PortletRenderer;
import oracle.portal.provider.v2.render.PortletContainerRenderer;
import oracle.portal.provider.v2.personalize.PortletPersonalizationManager;
import oracle.portal.provider.v2.security.PortletSecurityManager;
import oracle.portal.provider.v2.security.AccessControlException;
import oracle.portal.provider.v2.utils.ValidationError;
import oracle.portal.provider.v2.Validateable;
import oracle.portal.utils.xml.v2.*;
import oracle.webdb.provider.v2.builder.BuilderMetaData;

/**
 * <code>DefaultPortletDefinition</code> extends the base
 * <code>PortletDefinition</code> meta data class with a set of accessors
 * compatible with the {@link
 * oracle.portal.provider.v2.http.DefaultProviderLoader} mechanism that loads
 * provider definitions from XML files. It also further simplifies the {@link
 * PortletInstance}'s task by allowing definition of a number of 'controller
 * objects' to which it can delegate various parts of the portlet's
 * functionality. This is the class most commonly used in the PDK-Java samples
 * to define a portlet.<p>
 *
 * The table below summarizes the various portlet 'controller classes'.
 * <table>
 *
 * <tr><td>{@link PortletRenderer}</td><td>For rendering the portlet's visual
 * representation in all the various 'render modes' that it supports.</td></tr>
 *
 * <tr><td>{@link PortletContainerRenderer}</td><td>For rendering the portlet's
 * headers and footers with a standard 'look and feel'.</td></tr>
 *
 * <tr><td>{@link PortletPersonalizationManager}</td><td>For persisting portlet
 * customization data.</td></tr>
 *
 * <tr><td>{@link PortletSecurityManager}</td><td>For deciding which users can
 * access the portlet and its instances.</td></tr>
 *
 * </table>
 */
public class DefaultPortletDefinition extends PortletDefinition
    implements InitializableXMLObject
{

    protected PortletRenderer mRenderer;
    protected PortletSecurityManager mSecurityMgr;
    protected PortletPersonalizationManager mPersonalizationMgr;

    /**
     * Should be called immediately after construction of a
     * <code>DefaultPortletDefinition</code> to associate it with its parent
     * <code>ProviderDefinition</code>.
     * @param parent the parent provider definition object. Sould be a {@link
     * ProviderDefinition} or a subclass.
     */
    public void preInitialize(Object parent) throws NodeHandlerException
    {
        if (!(parent instanceof ProviderDefinition))
        {
            throw new NodeHandlerException("Parent of a PortletDefinition must be a ProviderDefinition");
        }
        init((ProviderDefinition)parent);
    }

    /**
     * Should be called to complete initialization of this
     * <code>DefaultPortletDefinition</code>.
     */
    public void postInitialize()
    {
    }

    /**
     * Defines the {@link PortletInstance} subclass that implements the
     * portlet's behaviour.
     * @param className the fully qualifed name of the {@link PortletInstance}
     * subclass that implements the portlet's behaviour.
     */
    public void setPortletInstanceClass(String className)
        throws ClassNotFoundException
    {
        // 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
        setPortletInstanceClass(Class.forName(className, true,
                                              Thread.currentThread().getContextClassLoader()));
    }

    /**
     * Sets the Portlet unique ID.
     *
     * @param id the Portlet (unique ID). Value should be a String
     * representation of a long value, unique with respect to the provider.
     * @exception PortletException if the given String does not represent a
     * valid <code>long</code>.
     */
    public void setId(String id) throws PortletException
    {
        try
        {
            setId(Long.parseLong(id));
        }
        catch (NumberFormatException e)
        {
            throw new PortletException(e);
        }
    }

    /**
     * Adds to the array of (input) parameters that this portlet recognizes
     * when requested to render to control/affect the rendering.
     *
     * @param parameter an object representing an input parameter. Should be a
     * {@link ParameterDefinition}.
     *
     * @exception PortletException if the given Object is not a valid {@link
     * oracle.portal.provider.v2.ParameterDefinition}
     */
    public void addInputParameter(Object parameter) throws PortletException
    {
        if (!(parameter instanceof ParameterDefinition))
        {
            throw new PortletException("Object not a ParameterDefinition");
        }
        super.addInputParameter((ParameterDefinition)parameter);
    }

    /**
     * Adds to the array of events that this portlet can raise to Portal
     *
     * @param event an object representing an event. Should be a {@link
     * oracle.portal.provider.v2.EventDefinition}.
     *
     * @exception PortletException if the given Object is not a valid {@link
     * EventDefinition}
     */
    public void addEvent(Object event) throws PortletException
    {
        if (!(event instanceof EventDefinition))
        {
            throw new PortletException("Object not an EventDefinition");
        }
        super.addEvent((EventDefinition)event);
    }

    /**
     * Sets the number of seconds the portal should wait before timing out a
     * request to this portlet.
     *
     * @param timeout timeout period, as a String representation of an integer
     * number of seconds.
     * @exception PortletException if the given String does not represent a
     * valid <code>int</code>.
     */
    public void setTimeout(String timeout) throws PortletException
    {
        try
        {
            setTimeout(Integer.parseInt(timeout));
        }
        catch (NumberFormatException e)
        {
            throw new PortletException(e);
        }
    }

    /**
     * Controls whether this portlet supports per user personalization.
     *
     * @param canEdit String representation of boolean value. Should be
     * <code>"true"</code> if this portlet is to support per user
     * personalization, <code>"false"</code> otherwise.
     */
    public void setShowEdit(String canEdit)
    {
        setShowEdit(XMLUtil.getBooleanValue(canEdit));
    }

    /**
     * Controls whether this portlet exposes per user personalization to the
     * public non-logged in user.
     *
     * @param canEdit String representation of boolean value. Should be
     * <code>"true"</code> if this portlet exposes per user personalization to
     * the public non-logged in user, <code>"false"</code> otherwise.
     *
     * @deprecated use {@link #setMinEditAuthLevel} to specify the minimum
     * authentication level needed to edit the portlet.
     */
    public void setShowEditToPublic(String canEditString)
    {
        boolean canEdit = XMLUtil.getBooleanValue(canEditString);
        if (canEdit)
        {
            mMinEditAuthLevel = ProviderUser.NOT_AUTHENTICATED;
        }
        else
        {
            mMinEditAuthLevel = ProviderUser.PUBLIC_STRONG_AUTH;
        }
    }

    /**
     * Controls whether this portlet supports per portlet customization. Per
     * portlet customizations means that all users of this instance see a
     * customized version of the portlet vs. the generic version. Users are
     * then free to further personalize this instance (via showEdit).
     *
     * @param canEditDefault String representation of boolean value. Should be
     * <code>"true"</code> if this portlet supports per portlet customization, <code>"false"</code>
     * otherwise.
     */
    public void setShowEditDefault(String canEditDefault)
    {
        setShowEditDefault(XMLUtil.getBooleanValue(canEditDefault));
    }


    /**
     * Controls whether this portlet is capable of displaying a design-time
     * preview of itself (after customization or personalization).
     *
     * @param canPreview String representation of boolean value. Should be
     * <code>"true"</code> if this portlet is capable of displaying a design-time preview of
     * itself (after customization or personalization), or <code>"false"</code> otherwise.
     */
    public void setShowPreview(String canPreview)
    {
        setShowPreview(XMLUtil.getBooleanValue(canPreview));
    }

    /**
     * Controls whether this portlet displays "detailed" mode. Detailed mode is
     * a full page mode where this portlet is expected to display a finer level
     * of information.
     *
     * @param hasDetails String representation of boolean value. Should be
     * <code>"true"</code> if this portlet is capable of displaying a "detailed" mode, or
     * <code>"false"</code> otherwise.
     */
    public void setShowDetails(String hasDetails)
    {
        setShowDetails(XMLUtil.getBooleanValue(hasDetails));
    }

    /**
     * Controls whether this portlet displays help information.
     *
     * @param hasHelp String representation of boolean value. Should be <code>"true"</code>
     * if this portlet is capable of displaying a help information, or <code>"false"</code>
     * otherwise.
     */
    public void setHasHelp(String hasHelp)
    {
        setHasHelp(XMLUtil.getBooleanValue(hasHelp));
    }

    /**
     * Controls whether this portlet displays about information.
     *
     * @param hasAbout String representation of boolean value. Should be <code>"true"</code>
     * if this portlet is capable of displaying about information, or <code>"false"</code>
     * otherwise.
     */
    public void setHasAbout(String hasAbout)
    {
        setHasAbout(XMLUtil.getBooleanValue(hasAbout));
    }

    /**
     * Sets the default Locale for this Portal. The Locale is specified using
     * the standard ISO 2 letter language and country codes,
     * e.g. <code>en.US</code>.
     *
     * @param lang a String specifying a Locale
     */
    public void setDefaultLocale(String lang)
    {
        Locale l = DefaultProviderDefinition.translateLocaleString(lang);
        setDefaultLocale(l);
    }

    /**
     * Controls whether this portlet is capable of rendering content for link
     * mode.
     *
     * @param value String representation of boolean value. Should be
     * <code>"true"</code> if this portlet is capable of rendering content for
     * link mode, <code>"false"</code> otherwise.
     */
    public void setShowLink(String value)
    {
        setShowLink(XMLUtil.getBooleanValue(value));
    }

    /**
     * Controls whether calls to {@link #hasAccess} should be made to determine
     * whether this portlet is runnable, before displaying it in the portlet
     * repository.
     *
     * @param value String representation of boolean value. Should be
     * <code>"true"</code> if access checks should be made on a per-user basis
     * before displaying the portlet in the portlet repository. If
     * <code>"false"</code>, the portlet is treated as a public portlet visible
     * to all users.
     */
    public void setCallIsRunnable(String value)
    {
        setCallIsRunnable(XMLUtil.getBooleanValue(value));
    }

    /**
     * Controls whether calls to retrieve this Portlet's meta data should be
     * made before displaying it in the portlet repository.
     *
     * @param value String representation of boolean value. If
     * <code>"true"</code>, portal will retrieve the portlet meta data before
     * displaying it in the portlet repository. If <code>"false"</code>, the
     * default meta data will be used for all users.
     */
    public void setCallGetPortlet(String value)
    {
        setCallGetPortlet(XMLUtil.getBooleanValue(value));
    }

    /**
     * Controls whether Portal will pass the requested page's URL parameters to
     * the portlet.<p>
     *
     * @param value String representation of boolean value.
     *
     * <ul><li>If <code>"true"</code>, then the portlet will receive all the
     *         (non-system) parameters from the page URL.</li>
     *
     *     <li>If <code>"false"</code> (the default) and the portlet declares input
     *         parameters, then the portlet will only receive values for its
     *         declared input parameters and private parameters which have been
     *         generated using {@link
     *         oracle.portal.provider.v2.render.PortletRendererUtil#portletParameter}. The
     *         parameter values will be determined by the usage of the portlet
     *         on a particular page.</li></ul>
     */
    public void setPassAllUrlParams(String value)
    {
        setPassAllUrlParams(XMLUtil.getBooleanValue(value));
    }

    /**
     * Sets the caching level used for portlet render requests.
     *
     * This controls the set of parameters sent to the provider in a 
     * render request and hence the "cache key" under which the returned
     * markup is stored. This overrides the User/Session information
     * entered for the provider on registration.
     *
     * The possible settings are:
     *
     * <ul>
     * <li>
     *    <code>session</code>: both user and portal session information
     *    is required.
     * </li>
     * <li>
     *    <code>user</code>: user information is required but no session
     *    information is required.
     *
     * </li>
     * <li>
     *    <code>system</code>: neither user information nor portal session
     *    information is required.
     * </li>
     * </ul>
     *
     * @param cacheLevel String indicates the caching level - should be one of
     *                    PortletConstants.CACHE_LEVEL_SYSTEM_STR,
     *                    PortletConstants.CACHE_LEVEL_USER_STR,  or
     *                    PortletConstants.CACHE_LEVEL_SESSION_STR.
     *
     * The cache level for a portlet render request is determined using
     * the following order of precedence:
     *
     * <ul>
     * <li> If specified, the portlet setting is used</li>
     * <li> If specified, the provider setting is used</li>
     * <li> If the provider is registered as "public" then the portlet will be
     *      called using the "public" user and cached at the system level</li>
     * <li> If the provider is registered as "user" and "portal session data"
     *      is requested then portlet will be cached at the session level</li>
     * <li> Otherwise, the portlet will be cached at user level</li>
     * </ul>
     */
    public void setCacheLevel(String cacheLevel) throws PortletException
    {
        if (cacheLevel.equalsIgnoreCase(
            PortletConstants.CACHE_LEVEL_SYSTEM_STR))
        {
            setCacheLevel(PortletConstants.CACHE_LEVEL_SYSTEM);
        }
        else if (cacheLevel.equalsIgnoreCase(
            PortletConstants.CACHE_LEVEL_SESSION_STR))
        {
            setCacheLevel(PortletConstants.CACHE_LEVEL_SESSION);
        }
        else if (cacheLevel.equalsIgnoreCase(
            PortletConstants.CACHE_LEVEL_USER_STR))
        {
            setCacheLevel(PortletConstants.CACHE_LEVEL_USER);
        }
        else
        {
            throw new PortletException("Invalid cacheLevel value. Must be one of: system, user, or session.");
        }
    }

    /**
     * Controls whether or not URL rewriting will be performed on the results
     * of a portlet render request.<p>
     *
     * If this method is called then the specified setting will override any
     * equivalent provider setting.
     * If this method is not called then the equivalent provider setting
     * will be used for this portlet.
     *
     * @param rewriteUrls String "true" if URL rewriting is to be performed.
     *        Otherwise "false".
     */
    public void setRewriteUrls(String rewriteUrls)
    {
        setRewriteUrls(XMLUtil.getBooleanValue(rewriteUrls));
    }

    /**
     * Sets the portlet's mobile settings.  This form allows you to set the
     * flags as a string.  The string can either be the (case insensitive)
     * delimited names of the flags or the number itself (as a String).  E.g.
     * passing "MOBILE_ONLY | INLINE_OUTPUT" is the same as passing the string
     * "3" to the method. See the {@link MobileFlags} class for the possible
     * flags and their String names.
     *
     * @param the portlet's mobile flags expressed as a String.
     */
    public void setMobileFlags(String flags)
        throws IllegalArgumentException
    {
        if (mMobileFlags != null)
        {
            mMobileFlags.setValue(flags);
        }
        else
        {
            mMobileFlags = new MobileFlags(flags);
        }
    }

    /**
     * Sets the locale personalization level for portlets defined by this
     * {@link PortletDefinition}. For more information regarding the locale
     * personalization level, see {@link
     * PortletDefinition#setLocalePersonalizationLevel(int)}.
     * 
     * <P>The value supplied should be one of</P>
     * 
     * <P><ul>
     *   <li>{@link PortletConstants#PERSONALIZATION_LEVEL_NONE_STR}</li>
     *   <li>{@link PortletConstants#PERSONALIZATION_LEVEL_LANGUAGE_STR}</li>
     *   <li>{@link PortletConstants#PERSONALIZATION_LEVEL_LOCALE_STR}</li>
     * </ul></P>
     * 
     * <P>Failing that, a {@link PortletException} will result.</P>
     * 
     * @param value the string representation of the locale personalization
     * level.
     * @throws PortletException if the value is not recognized.
     */
    public void setLocalePersonalizationLevel(String value)
        throws PortletException
    {
        if (PortletConstants.PERSONALIZATION_LEVEL_NONE_STR.equalsIgnoreCase(value))
        {
            setLocalePersonalizationLevel(PortletConstants.PERSONALIZATION_LEVEL_NONE);
        }
        else if (PortletConstants.PERSONALIZATION_LEVEL_LANGUAGE_STR.equalsIgnoreCase(value))
        {
            setLocalePersonalizationLevel(PortletConstants.PERSONALIZATION_LEVEL_LANGUAGE);
        }
        else if (PortletConstants.PERSONALIZATION_LEVEL_LOCALE_STR.equalsIgnoreCase(value))
        {            
            setLocalePersonalizationLevel(PortletConstants.PERSONALIZATION_LEVEL_LOCALE);
        }
        else
        {
            throw new PortletException("Invalid localPersonalizationLevel");
        }
    }


    /**
     * Sets the controller that will render this portlet's container.<p>
     *
     * The container renderer is responsible for rendering the portlet
     * container in each of the show modes the portlet supports.
     *
     * @param renderer the portlet container renderer. Even though the type of
     * the parameter is Object, the parameter must be an instance of {@link
     * PortletContainerRenderer}.
     * @exception PortletException if the given object is not a valid
     * <code>PortletContainerRenderer</code>.
     */
    public void setContainerRenderer(Object renderer) throws PortletException
    {
        if (renderer != null && renderer instanceof PortletContainerRenderer)
        {
            super.setContainerRenderer((PortletContainerRenderer)renderer);
        }
        else
        {
            throw new PortletException("Object must be an instance of PortletContainerRenderer");
        }
    }
    
    
    /**
     * Gets the security manager for this portlet.  The security manager is
     * used to check whether a user is authorized to perform a specific action.
     * It is valid for a portlet to not have a security manager.  I.e. this
     * method can return null.  If there is no security manager it is assumed
     * that all users have unrestricted access.
     * @return the security manager for this portlet if it has one, or
     * <code>null</code> otherwise.
     */
    public PortletSecurityManager getSecurityManager()
    {
        return mSecurityMgr;
    }

    /**
     * Gets the personalization manager for this portlet.  The personalization
     * manager provides the interface between the renderer and the
     * personalization store that allows the renderer to get the user
     * customizations for this rendition.  It is valid for a portlet to not
     * have a personalization manager.  I.e. this method can return null.
     * @return the personalization manager for this portlet if it has one, or
     * <code>null</code> otherwise.
     */
    public PortletPersonalizationManager getPersonalizationManager()
    {
        return mPersonalizationMgr;
    }

    /**
     * Gets the renderer for this portlet.  The renderer is responsible for
     * emitting the visual aspects of the portlet (as markup).  All
     * <code>DefaultPortletDefinition</code>s must have a renderer.
     * @return the renderer for this portlet.
     */
    public PortletRenderer getRenderer()
    {
        return mRenderer;
    }

    /**
     * Sets the portlet security manager.
     *
     * @param mgr the portlet security manager. Should be a {@link
     * PortletSecurityManager}.
     * @exception PortletException if the given Object is not a valid {@link
     * PortletSecurityManager}
     */
    public void setSecurityManager(Object mgr) throws PortletException
    {
        if (!(mgr instanceof PortletSecurityManager))
        {
            throw new PortletException("Object not a PortletSecurityManager");
        }
        if (mSecurityMgr != null)
        {
            throw new PortletException("SecurityManager already set.");
        }
        mSecurityMgr = (PortletSecurityManager)mgr;
    }

    /**
     * Sets the portlet personalization manager.
     *
     * @param mgr the personalization manager. Should be a {@link
     * PortletPersonalizationManager}.
     * @exception PortletException if the given Object is not a valid {@link
     * PortletPersonalizationManager}
     */
    public void setPersonalizationManager(Object mgr) throws PortletException
    {
        if (!(mgr instanceof PortletPersonalizationManager))
        {
            throw new PortletException("Object not a PortletPersonalizationManager");
        }
        mPersonalizationMgr = (PortletPersonalizationManager)mgr;
    }

    /**
     * Sets the portlet renderer.
     *
     * @param renderer the renderer. Should be a {@link PortletRenderer}.
     * @exception PortletException if the given Object is not a valid {@link
     * PortletRenderer}
     */
    public void setRenderer(Object renderer) throws PortletException
    {
        if (!(renderer instanceof PortletRenderer))
        {
            throw new PortletException("Object not a PortletRenderer");
        }
        mRenderer = (PortletRenderer)renderer;
    }

    /**
     * Called on each of a Provider instance's <code>PortletDefinition</code>s
     * when it is first registered on a Portal. This gives a
     * <code>DefaultPortletDefinition</code> a chance to ask its {@link
     * PortletPersonalizationManager} to initialize storage for the portlet.
     */
    public void register(ProviderInstance pi)
        throws IOException, AccessControlException
    {
        if (mPersonalizationMgr != null)
        {
            mPersonalizationMgr.init(this);
        }
    }

    /**
     * Called on each of a Provider instance's <code>PortletDefinition</code>s
     * when the provider is deregistered from a Portal. This gives a
     * <code>DefaultPortletDefinition</code> a chance to ask its {@link
     * PortletPersonalizationManager} to release storage for the portlet.
     */
    public void deregister(ProviderInstance pi)
        throws IOException, AccessControlException
    {
        if (mPersonalizationMgr != null)
        {
            mPersonalizationMgr.destroy(pi, this);
        }
    }

    /**
     *  Verifies whether the user is authorized to use instances of this
     *  <code>PortletDefinition</code>. That is, does this user have the
     *  necessary privileges to create, get, copy, and destroy instances of
     *  this <code>PortletDefinition</code>. A
     *  <code>DefaultPortletDefinition</code> is able to use its {@link
     *  PortletSecurityManager} to perform this check.
     *
     *  @param  user the current user we are being asked to authorize.
     *  @return <code>true</code> is this user is authorized to access this
     *  portlet.
     */
    public boolean hasAccess(ProviderInstance pi,
                             ProviderUser user)
    {
        if (mSecurityMgr != null)
        {
            return mSecurityMgr.hasAccess(pi, this, user);
        }
        return true;
    }

    /**
    * Each portlet can specifiy the builder attached to edit this portlet.
    * This method sets a PortletBuilder with this portletdefinition.
    * @param portletBuilder the PortletBuilder  attribute for this Portlet
    * @exception PortletException if the given Object is not a valid {@link
    * oracle.webdb.provider.v2.builder.BuilderMetaData}.
    */
    public void setPortletBuilder(Object portletBuilder) throws PortletException
    {
          if (!(portletBuilder instanceof BuilderMetaData))
          {
              throw new PortletException("Object is not an instance of  "+
                 " PortletBuilder");
          }
          super.setPortletBuilder( (BuilderMetaData) portletBuilder );
    }    

    /**
     * Validates the internal state of this
     * <code>DefaultPortletDefinition</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, "DefaultPortletDefinition",
                                       getName(getDefaultLocale()));

        // Validate the supertype
        super.validate(ctx, errors);

        // Check the validity of the member variables
        if (mRenderer == null)
        {
            errors.addError(ctx, "Renderer class must be set", null);
        }

        // Validate appropriate members.
        if (mRenderer != null && mRenderer instanceof Validateable)
        {
            ((Validateable)mRenderer).validate(ctx, errors);
        }
        if (mSecurityMgr != null && mSecurityMgr instanceof Validateable)
        {
            ((Validateable)mSecurityMgr).validate(ctx, errors);
        }
        if (mPersonalizationMgr != null &&
            mPersonalizationMgr instanceof Validateable)
        {
            ((Validateable)mPersonalizationMgr).validate(ctx, errors);
        }
    }    
}

