/*  Copyright (c) 2000, 2003  Oracle Corporation. All Rights Reserved. */
package oracle.portal.provider.v2;

import java.io.IOException;
import java.util.*;
import oracle.portal.provider.v2.preference.PreferenceStore;
import oracle.portal.provider.v2.preference.PreferenceStoreException;
import oracle.portal.provider.v2.security.AccessControlException;
import oracle.portal.provider.v2.render.PortletContainerRenderer;
import oracle.portal.provider.v2.render.DefaultContainerRenderer;
import oracle.portal.provider.v2.Validateable;
import oracle.portal.provider.v2.utils.ValidationError;
import oracle.webdb.provider.v2.builder.BuilderMetaData;

import oracle.portal.provider.v2.ProviderConstants;

/**
 * A <code>PortletDefinition</code> is a sub-part of a {@link
 * ProviderDefinition} that defines the properties of a single portlet. Again,
 * a <code>PortletDefinition</code> is simply a container for meta data, and
 * drives the behaviour of one or more {@link PortletInstance} objects which
 * perform the portlet's functions.  Users who require a richer set of
 * portlet-level properties or accessors to these properties should feel free
 * to use their own subclass.<p>
 *
 * It is recommended that you support multiple languages within portlet
 * meta-data. This is accomplished by storing text in resource bundles and then
 * registering the resource bundle by setting the <code>resource</code>
 * property.<p>
 *
 * The following <code>PortletDefinition</code> properties can be specified via
 * a resource bundle: <code>name</code>, <code>title</code>,
 * <code>shortTitle</code>, <code>description</code>, and
 * <code>timeoutMessage</code>. The {@link PortletConstants} interface defines
 * constants for the resource names used to extract these properties from the
 * resource file. Note that a resource look up for these properties will only
 * occur if they are not set explicitly on the
 * <code>PortletDefinition</code>.<p>
 *
 * It is also possible to define your own specific resource keys for each of
 * the translatable strings by using the following properties:
 * <code>nameKey</code>, <code>titleKey</code>, <code>shortTitleKey</code>,
 * <code>descriptionKey</code>, and <code>timeoutMessageKey</code>.
 */
public class PortletDefinition implements Validateable
{

    protected Class mInstanceClass = DefaultPortletInstance.class;
    protected ProviderDefinition mProviderDefinition;
    protected String mName;
    protected long mPortletId;
    protected String mTitle;
    protected String mShortTitle;
    protected String mDescription;
    protected Vector mAcceptContentTypes;
    protected int mTimeout;
    protected String mTimeoutMessage;
    
    // by default, user must be logged on to customize a page
    protected int mMinEditAuthLevel = ProviderUser.USER_STRONG_AUTH;
    
    protected boolean mShowEdit = false;
    protected boolean mShowEditDefault = false;
    protected boolean mShowPreview = false;
    protected boolean mShowDetails = false;
    protected boolean mHasHelp = false;
    protected boolean mHasAbout = false;
    protected boolean mShowLink = false;

    // defaulting both mCallIsRunnable and mCallGetPortlet to false 
    // to optimize performance.
    protected boolean mCallIsRunnable = false;
    protected boolean mCallGetPortlet = false;
    
    // default to an "empty" set of flags so always set.
    protected MobileFlags mMobileFlags = new MobileFlags(MobileFlags.MOBILE_GENERIC);

    protected String mResource;
    protected ResourceBundle mBundle;
    protected Locale mBundleLocale;

    protected Locale mDefaultLocale = Locale.getDefault();
    protected PreferenceStore mDefaultPreferenceStore;
    protected PortletContainerRenderer mContainerRenderer;

    /**
     * Lock object used in synchronize blocks
     */
    protected Object mLock = new Object();

    /**
     * mNameKey stores the string value for the key used to look
     * up the portlet's name in a resource bundle.
     */
    private String mNameKey;

    /**
     * mTitleKey stores the string value for the key used to look
     * up the portlet's title in a resource bundle.
     */
    private String mTitleKey;

    /**
     * mShortTitleKey stores the string value for the key used to look
     * up the portlet's short title in a resource bundle.
     */
    private String mShortTitleKey;

    /**
     * mDescriptionKey stores the string value for the key used to look
     * up the portlet's description in a resource bundle.
     */
    private String mDescriptionKey;

    /**
     * mTimeoutMessageKey stores the string value for the key used to look
     * up the portlet's timeout message in a resource bundle.
     */
    private String mTimeoutMessageKey;

    /**
     * mPassAllUrlParams stores the Boolean value for the portlet.
     *
     * It is declared as a Boolean object so that it may have a null value.
     * This is required to track that the equivalent tag was not set in
     * provider.xml for this portlet.
     */
    private Boolean mPassAllUrlParams;

    /**
     * mInputParameters stores the set of ParameterDefinitions for the portlet.
     */
    private List mInputParameters;

    /**
     * mEvents stores the set of EventDefinitions for the portlet.
     */
    private List mEvents;

    /**
     * mBuilderMetaData stores the PortletBuilder  attributes
     */
    private BuilderMetaData mBuilderMetaData;

    /**
     * mCacheLevel stores the level of caching for the portlet.
     */
    private int mCacheLevel = PortletConstants.CACHE_LEVEL_UNSPECIFIED;

    /**
     * mRewriteUrls stores whether or not URL rewriting will be performed 
     * on the portlet's rendered output.
     */
    private int mRewriteUrls = PortletConstants.REWRITE_URLS_UNSPECIFIED;

    private int mLocalePersonalizationLevel =
        PortletConstants.PERSONALIZATION_LEVEL_UNSPECIFIED;

    /**
     * This is used to determine if we need to query for the default locale at
     * provider level. It's not safe to simply use the state of mDefaultLocale
     * (i.e == null).
     */
    private boolean mDefaultLocaleSpecified = false;

    public PortletDefinition()
    {
        mAcceptContentTypes = new Vector(1);
        mAcceptContentTypes.addElement("text/html");
    }

    /**
     * Should be called immediately after construction of a
     * <code>PortletDefinition</code> to associate it with its parent
     * <code>ProviderDefinition</code>.
     * @param pd the parent provider definition.
     */
    public void init(ProviderDefinition pd)
    {
        mProviderDefinition = pd;
    }

    /**
     * @deprecated All validation is handled by the {@link #validate(String,
     * ValidationError)} method specified by the {@link Validateable}
     * interface.
     */
    public void validate() throws PortletException
    {
    }

    /**
     * Returns the <code>Class</code> representing the {@link PortletInstance}
     * class or subclass that implements the portlet's behaviour.
     * @return a <code>Class</code> representing the {@link PortletInstance}
     * subclass that implements the portlet's behaviour.
     */
    public Class getPortletInstanceClass()
    {
        return mInstanceClass;
    }


    /**
     * Defines the {@link PortletInstance} subclass that implements the
     * portlet's behaviour.
     * @param c a <code>Class</code> representing the {@link PortletInstance}
     * subclass that implements the portlet's behaviour.
     */
    public void setPortletInstanceClass(Class c)
    {
        mInstanceClass = c;
    }

    /**
     * Gets the ProviderDefinition that owns this portlet.
     *
     * @return the ProviderDefinition that owns this portlet.
     */
    public ProviderDefinition getProviderDefinition()
    {
        return mProviderDefinition;
    }


    /**
     * Gets this Portlet's default locale.  If this has not been specified with
     * either {@link #setDefaultLocale} or {@link
     * ProviderDefinition#setDefaultLocale(Locale)}, the VM's default locale is
     * returned.
     */
    public Locale getDefaultLocale()
    {
        if (mDefaultLocaleSpecified)
        {
            return mDefaultLocale;            
        }
        else
        {
            return mProviderDefinition.getDefaultLocale();
        }
    }

    /**
     * Gets the string resource (bundle) for this Portlet.
     *
     * @param l the Locale of the resource bundle being requested.
     * @return the string resource (bundle) for this Portlet, or <b>null</b> if
     * there isn't one.
     */
    public ResourceBundle getResourceBundle(Locale l)
    {
        if (mResource == null) return null;

        if (l == null)
        {
            l = Locale.getDefault();
        }

        if (mBundle == null || mBundleLocale == null || !l.equals(mBundleLocale))
        {
            // We have to use the the current thread's 'context class loader'
            // (i.e. the web app's class loader) to load the resource, so that
            // it can be picked up from within the web app
            mBundle = ResourceBundle.getBundle(mResource, l,
                                               Thread.currentThread().getContextClassLoader());
            mBundleLocale = l;
        }

        return mBundle;
    }

    /**
     * Sets the name of the default resource bundle class for this Portlet.
     *
     * @param resourceClassName full name of class extending {@link
     * java.util.ResourceBundle}.
     */
    public void setResource(String resourceClassName)
    {
        mResource = resourceClassName;
    }

    /**
     * Gets the portlet name.
     *
     * @param l the Locale (language) the requested name should be
     * retrieved/returned in.
     * @return the portlet name.  This is the Portlet's non-unique display
     * name.
     */
    public String getName(Locale l)
    {
        return findTranslatedString(mName, getNameKey(),
                                    PortletConstants.NAME, l);
    }

    /**
     * Gets the the key used to look up this portlet's name from a
     * resource bundle.
     *
     * @return the key used to look up this portlet's name from a
     * resource bundle.
     */
    public String getNameKey()
    {
        return mNameKey;
    }

    /**
     * Gets the portlet unique ID.
     *
     * @return the portlet (unique) ID.  Value should be unique with respect to
     * the Provider.
     */
    public long getId()
    {
        return mPortletId;
    }

    /**
     * Gets the portlet title.
     *
     * @param l the Locale (language) the requested title should be
     * retrieved/returned in.
     * @return the title of this Portlet.  This is the name that shows up in a
     * portlet's title bar.
     */
    public String getTitle(Locale l)
    {
        return findTranslatedString(mTitle, getTitleKey(),
                                    PortletConstants.TITLE, l);
    }

    /**
     * Gets the key used to look up this portlet's title from a resource
     * bundle.
     *
     * @return the key used to look up this portlet's title from a
     * resource bundle.
     */
    public String getTitleKey()
    {
        return mTitleKey;
    }

    /**
     * Gets the portlet short title. This is the name used when
     * real estate is at a premium such as when rendering a mobile link.
     *
     * @param l the Locale (language) the requested short title should be
     * retrieved / returned in.
     * @return the short title of this Portlet.
     */
    public String getShortTitle(Locale l)
    {
        return findTranslatedString(mShortTitle, getShortTitleKey(),
                                    PortletConstants.SHORTTITLE, l);
    }


    /**
     * Gets the key used to look up this portlet's short title from a
     * resource bundle.
     *
     * @return the key used to look up this portlet's short title from a
     * resource bundle.
     */
    public String getShortTitleKey()
    {
        return mShortTitleKey;
    }

    /**
     * Gets the portlet description. This is displayed at design-time along
     * with the name when a user is choosing to add a portlet to a page.
     *
     * @param l the Locale (language) the requested description should be
     * retrieved/returned in.
     * @return a short description of what this portlet is.
     */
    public String getDescription(Locale l)
    {
        return findTranslatedString(mDescription, getDescriptionKey(),
                                    PortletConstants.DESCRIPTION, l);
    }

    /**
     * Gets the the key used to look up this portlet's description from a
     * resource bundle.
     *
     * @return the key used to look up this portlet's description from a
     * resource bundle.
     */
    public String getDescriptionKey()
    {
        return mDescriptionKey;
    }

    /**
     * Gets an array of (input) parameters that this portlet recognizes when
     * requested to render to control/affect the rendering.
     *
     * @return array of <code>ParameterDefinition</code>s.
     */
    public ParameterDefinition[] getInputParameters()
    {
        if (mInputParameters == null)
        {
            return null;
        }
        ParameterDefinition[] inputParameters =
            new ParameterDefinition[mInputParameters.size()];

        mInputParameters.toArray(inputParameters);

        return inputParameters;
    }

    /**
     * Gets the array of events that this portlet can raise to Portal.
     *
     * @return array of <code>EventDefinition</code>s.
     */
    public EventDefinition[] getEvents()
    {
        if (mEvents == null)
        {
            return null;
        }

        // Make a copy and return
        EventDefinition[] events =
            new EventDefinition[mEvents.size()];

        mEvents.toArray(events);

        return events;
    }

    /**
     * Gets an array of mime-types (represented as Strings) that this Portlet
     * can render.  For example <code>{"text/html", "text/xml"}</code>.
     *
     * @return array containing a list of mime-types.
     */
    public String[] getAcceptContentTypes()
    {
        if (mAcceptContentTypes == null)
        {
            return null;
        }
        String[] contentTypes =
            new String[mAcceptContentTypes.size()];

        mAcceptContentTypes.copyInto(contentTypes);

        return contentTypes;
    }

    /**
     * Gets the number of seconds the portal should wait before timing out a
     * request to this portlet.
     *
     * @return timeout period in seconds.
     */
    public int getTimeout()
    {
        return mTimeout;
    }

    /**
     * Gets the timeout messsage that the portal should use in its response
     * when a request to this portlet times out.
     *
     * @param l the Locale (language) the requested timeout message should be
     * retrieved/returned in.
     * @return Timeout message.
     */
    public String getTimeoutMessage(Locale l)
    {
        return findTranslatedString(mTimeoutMessage, getTimeoutMessageKey(),
                                    PortletConstants.TIMEOUTMSG, l);
    }

    /**
     * Gets the the key used to look up this portlet's timeout message from a
     * resource bundle.
     *
     * @return the key used to look up this portlet's timeout message from a
     * resource bundle.
     */
    public String getTimeoutMessageKey()
    {
        return mTimeoutMessageKey;
    }

    /**
     * Returns <code>true</code> if this portlet supports per user
     * personalization, <code>false</code> otherwise.
     *
     * @return <code>true</code> if this portlet supports per user
     * personalization, <code>false</code> otherwise.
     */
    public boolean hasShowEdit()
    {
        return mShowEdit;
    }

    /**
     * Returns <code>true</code> if this portlet supports per portlet
     * customization, <code>false</code> otherwise.  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).
     *
     * @return <code>true</code> if this portlet supports per portlet
     * customization, <code>false</code> otherwise.
     */
    public boolean hasShowEditDefault()
    {
        return mShowEditDefault;
    }

    /**
     * Returns <code>true</code> if this portlet is capable of displaying a
     * design-time preview of itself (after customization or
     * personalization). Returns <code>false</code> otherwise.
     *
     * @return <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 boolean hasShowPreview()
    {
        return mShowPreview;
    }

    /**
     * Returns <code>true</code> if this portlet is capable of displaying a
     * full-screen (detailed) version of itself.
     *
     * @return <code>true</code> if this portlet is capable of displaying a
     * full-screen (detailed) version of itself.
     *
     */
    public boolean hasShowDetails()
    {
        return mShowDetails;
    }

    /**
     * Returns <code>true</code> if this portlet is capable of displaying help
     * information.
     *
     * @return <code>true</code> if this portlet is capable of displaying help
     * information.
     *
     */
    public boolean hasHelp()
    {
        return mHasHelp;
    }

    /**
     * Returns <code>true</code> if this portlet is capable of displaying about
     * information.
     *
     * @return <code>true</code> if this portlet is capable of displaying about
     * information.
     *
     */
    public boolean hasAbout()
    {
        return mHasAbout;
    }

    /**
     * Returns <code>true</code> if this portlet is capable of rendering content for link
     * mode.
     *
     * @return <code>true</code> if this portlet is capable of rendering
     * content for link mode.
     */
    public boolean hasShowLink()
    {
        return mShowLink;
    }

    /**
     *
     * Returns <code>true</code> if calls to {@link #hasAccess} should be made
     * to determine whether this portlet is runnable, before displaying it in
     * the portlet repository. If <code>false</code>, the portlet is treated as
     * a public portlet visible to all users.
     *
     * @return <code>true</code> if calls to {@link #hasAccess} should be made
     * to this Portlet.
     */
    public boolean callIsRunnable()
    {
        return mCallIsRunnable;
    }

    /**
     * Returns <code>true</code> if calls to retrieve this portlet's meta data
     * should be made before displaying in the portlet repository.  If
     * <code>false</code>, the default meta data will be used for all users.
     *
     * @return returns <code>true</code> if calls to retrieve this portlet's
     * meta data should be made.
     */
    public boolean callGetPortlet()
    {
        return mCallGetPortlet;
    }

    /**
     * Returns the portlet's mobile settings as a bitwise flag.  The flags
     * values are defined in the class {@link MobileFlags}.
     *
     * @return the portlet's mobile setting in a bitwise flag
     */
    public MobileFlags getMobileFlags()
    {
        return mMobileFlags;
    }


    /**
     * Determines whether Portal should always pass the requested page's URL
     * parameters to the portlet.
     * @return boolean indicating whether Portal will always pass the requested
     * page's URL parameters to the portlet.
     * @see #setPassAllUrlParams
     */
    public boolean getPassAllUrlParams()
    {
        // If the portlet didn't specify a value, then get the providers.
        if (mPassAllUrlParams == null)
        {
           return mProviderDefinition.getPassAllUrlParams();
        }
        else
        {
           return mPassAllUrlParams.booleanValue();
        }
    }

    /**
     * Returns the caching level used for portlet render requests.<p>
     *
     * @return int the portlet's cache level.
     *
     * @see #setCacheLevel
     */
    public int getCacheLevel()
    {
        // If the portlet didn't specify a value then get the provider's value.
        if (mCacheLevel == PortletConstants.CACHE_LEVEL_UNSPECIFIED)
        {
           return mProviderDefinition.getCacheLevel();
        }
        else
        {
           return mCacheLevel;
        }
    }

    /**
     * Determines whether URL rewriting will be performed on the result of
     * a portlet render request.
     *
     * @return boolean true if URL rewriting is to be performed.
     *         Otherwise false.
     *
     * @see #setRewriteUrls
     */
    public boolean getRewriteUrls()
    {
        // If the portlet didn't specify a value then get the provider's value.
        if (mRewriteUrls == PortletConstants.REWRITE_URLS_UNSPECIFIED)
        {
           return mProviderDefinition.getRewriteUrls();
        }
        else
        {
           return mRewriteUrls == PortletConstants.REWRITE_URLS_TRUE;
        }
    }

    /**
     * Sets the portlet name.
     *
     * @param name the portlet name. This is the Portlet's non-unique display
     * name.
     */
    public void setName(String name)
    {
        mName = name;
    }

    /**
     * Sets the key used to look up the portlet's name in a resource bundle.
     *
     * @param nameKey the string value for the key used to look up 
     * the portlet's name in a resource bundle.
     */
    public void setNameKey(String nameKey)
    {
        mNameKey = nameKey;
    }

    /**
     * Sets the portlet unique ID.
     *
     * @param id the portlet (unique ID). Value should be a long value, unique
     * with respect to the Provider.
     */
    public void setId(long id)
    {
        mPortletId = id;
    }

    /**
     * Sets the portlet title.
     *
     * @param title the title of this Portlet. This is the name that shows up
     * in a portlet's title bar.
     */
    public void setTitle(String title)
    {
        mTitle = title;
    }

    /**
     * Sets the portlet title key.
     *
     * @param titleKey the string value for the key used to look up 
     * the portlet's title in a resource bundle.
     */
    public void setTitleKey(String titleKey)
    {
        mTitleKey = titleKey;
    }

    /**
     * Sets the portlet short title.
     *
     * @param sthe short title of this Portlet. This is the name used when real
     * estate is at a premium such as when rendering a mobile link.
     */
    public void setShortTitle(String title)
    {
        mShortTitle = title;
    }

    /**
     * Sets the key used to look up the portlet's short title in a resource
     * bundle.
     *
     * @param shortTitleKey the string value for the key used to look up 
     * the portlet's short title in a resource bundle.
     */
    public void setShortTitleKey(String shortTitleKey)
    {
        mShortTitleKey = shortTitleKey;
    }

    /**
     * Sets the portlet description.
     *
     * @param description a short description of what this portlet is.
     * Displayed at design-time along with the name and thumbnail image when a
     * user is choosing to add a portlet to a page.
     */
    public void setDescription(String description)
    {
        mDescription = description;
    }

    /**
     * Sets the key used to look up the portlet's description in a resource
     * bundle.
     *
     * @param descriptionKey the string value for the key used to look up 
     * the portlet's description in a resource bundle.
     */
    public void setDescriptionKey(String descriptionKey)
    {
        mDescriptionKey = descriptionKey;
    }

    /**
     * Adds to the array of (input) parameters that this portlet recognizes
     * when requested to render to control/affect the rendering.
     *
     * @param parameter a ParameterDefinition.
     */
    public void addInputParameter(ParameterDefinition parameter)
    {
        if (mInputParameters == null)
        {
            // guess an initial number of 4 parameters for a portlet
            mInputParameters = new ArrayList(4);
        }
        mInputParameters.add(parameter);
    }

    /**
     * Adds to the array of events that this portlet can raise to Portal.
     *
     * @param event an EventDefinition.
     */
    public void addEvent(EventDefinition event)
    {
        if (mEvents == null)
        {
            // guess an initial number of 4 events for a portlet
            mEvents = new ArrayList(4);
        }
        mEvents.add(event);
    }

    /**
     * Adds to the set of mime-types (represented as Strings) that this Portlet
     * can render.
     *
     * @param type mime-type String, e.g. "text/html" or "text/xml".
     */
    public void addAcceptContentType(String type)
    {
        // Add the accept type if it does not already exist
        if (type != null && !mAcceptContentTypes.contains(type))
        {
            mAcceptContentTypes.addElement(type);
        }
    }

    /**
     * Sets the number of seconds the portal should wait before timing out a
     * request to this portlet.
     *
     * @param timeout integer number of seconds.
     */
    public void setTimeout(int timeout)
    {
        mTimeout = timeout;
    }

    /**
     * Sets the timeout messsage that the portal should use in its response
     * when a request to this portlet times out.
     *
     * @param msg timeout message.
     */
    public void setTimeoutMessage(String msg)
    {
        mTimeoutMessage = msg;
    }

    /**
     * Sets the portlet timeout message key.
     *
     * @param timeoutMessageKey the string value for the key used to look up 
     * the portlet's timeout message in a resource bundle.
     */
    public void setTimeoutMessageKey(String timeoutMessageKey)
    {
        mTimeoutMessageKey = timeoutMessageKey;
    }

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


    /**
     * Controls whether this Portlet exposes per user personalization to the
     * public non-logged in user.
     *
     * @param canEdit set to <code>true</code> to expose 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(boolean canEdit)
    {
        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 Should be <code>true</code> if this Portlet
     * supports per portlet customization, <code>false</code> otherwise.
     */
    public void setShowEditDefault(boolean canEditDefault)
    {
        mShowEditDefault = canEditDefault;
    }


    /**
     * Controls whether this portlet is capable of displaying a design-time
     * preview of itself (after customization or personalization).
     *
     * @param canPreview 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(boolean canPreview)
    {
        mShowPreview = 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 Should be <code>true</code> if this portlet is capable
     * of displaying a "detailed" mode, or <code>false</code> otherwise.
     */
    public void setShowDetails(boolean hasDetails)
    {
        mShowDetails = hasDetails;
    }

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

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

    /**
     * Sets the default Locale for this Portal
     *
     * @param l a <code>Locale</code>
     */
    public void setDefaultLocale(Locale l)
    {
        mDefaultLocale = l;
        mDefaultLocaleSpecified = true;
    }

    /**
     * Controls whether this portlet is capable of rendering content for link
     * mode.
     *
     * @param value boolean flag.
     */
    public void setShowLink(boolean value)
    {
        mShowLink = 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 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(boolean value)
    {
        mCallIsRunnable = value;
    }

    /**
     * Controls whether calls to retrieve this Portlet's meta data should be
     * made before displaying it in the portlet repository.
     *
     * @param 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(boolean value)
    {
        mCallGetPortlet = value;
    }

    /**
     * Sets the portlet's mobile settings.  The flags values are defined by the
     * {@link MobileFlags} class.
     *
     * @param the portlet's mobile setting in a bitwise flag
     */
    public void setMobileFlags(long flags)
    {
        if (mMobileFlags != null)
        {
            mMobileFlags.setValue(flags);
        }
        else
        {
            mMobileFlags = new MobileFlags(flags);
        }
    }

    /**
     * Controls whether Portal will pass the requested page's URL parameters to
     * the portlet.<p>
     *
     * @param value boolean indicating whether Portal should always pass the
     * requested page's URL parameters to the portlet.
     *
     * <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(boolean value)
    {
        mPassAllUrlParams = new Boolean(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 int indicates the caching level - can be one of
     *                    PortletConstants.CACHE_LEVEL_SYSTEM, 
     *                    PortletConstants.CACHE_LEVEL_USER,  or
     *                    PortletConstants.CACHE_LEVEL_SESSION.
     *
     * 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(int cacheLevel) throws PortletException
    {
        if (cacheLevel < PortletConstants.CACHE_LEVEL_SYSTEM ||
            cacheLevel > PortletConstants.CACHE_LEVEL_SESSION)
        {
            throw new PortletException("Invalid cacheLevel value. Must be one of: PortletConstants.CACHE_LEVEL_SYSTEM, PortletConstants.CACHE_LEVEL_USER, or PortletConstants.CACHE_LEVEL_SESSION.");
        }

        mCacheLevel = cacheLevel;
    }

    /**
     * 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 boolean true if URL rewriting is to be performed.
     *        Otherwise false.
     */
    public void setRewriteUrls(boolean rewriteUrls)
    {
        if (rewriteUrls)
        {
            mRewriteUrls = PortletConstants.REWRITE_URLS_TRUE;
        }
        else
        {
            mRewriteUrls = PortletConstants.REWRITE_URLS_FALSE;
        }
    }

    /**
     * Selects the default {@link PreferenceStore} that should be used by the
     * portlet and all its child objects for which a preference store has not
     * been chosen explicitly.
     * @param preferenceStoreName the name of one of the
     * <code>PreferenceStore</code>s declared in the {@link
     * ProviderDefinition}.
     * @exception PreferenceStoreException if a <code>PreferenceStore</code>
     * with the given name has not been defined.
     * @see #getPreferenceStore()
     */
    public void setPrefStoreName(String preferenceStoreName)
        throws PreferenceStoreException
    {
        mDefaultPreferenceStore = mProviderDefinition.getPreferenceStore(preferenceStoreName);
    }

    /**
     * Gets the default <code>PreferenceStore</code> that should be used by the
     * portlet and all its child objects for which a preference store has not
     * been chosen explicitly. If this has not been set with {@link
     * #setPrefStoreName}, then the provider's default preference store,
     * returned by {@link ProviderDefinition#getPreferenceStore()} will be
     * returned if one has been defined, or <code>null</code> otherwise.
     * @return the <code>PreferenceStore</code> that should be used by child
     * objects by default if one has been defined, or <code>null</code>
     * otherwise.
     */
    public PreferenceStore getPreferenceStore()
    {
        if(mDefaultPreferenceStore == null)
        {
            return mProviderDefinition.getPreferenceStore();
        }
        return mDefaultPreferenceStore;
    }

    /**
     * Determines whether this Portlet exposes per user personalization to the
     * public non-logged in user.
     *
     * @return <code>true</code> if the public (not logged in) user should be
     * allowed to personalize this portlet. Otherwise <code>false</code>.
     *
     * @deprecated use {@link #getMinEditAuthLevel} to specify the minimum
     * authentication level needed to edit the portlet
     */
    public boolean getShowEditToPublic()
    {
        return (getMinEditAuthLevel() == ProviderUser.NOT_AUTHENTICATED);
    }


    /**
     * Returns the minimum user authentication level required for the
     * customize link to be displayed in the portlet header
     *
     * If the authentication level of a user is less than the minimum
     * authentication level specified, the customize link will not be
     * displayed in the portlet header.
     *
     * @return the min. authentication level required to edit the portlet.
     */
    public int getMinEditAuthLevel()
    {
        return mMinEditAuthLevel;
    }


     /**
     * Sets the minimum user authentication level required for the
     * customize link to be displayed in the portlet header
     *
     * If the authentication level of a user is less than the minimum
     * authentication level specified, the customize link will not be
     * displayed in the portlet header.
     *
     * @param authLevel minimum authentication level. Possible values are
     *                  <code>"PUBLIC"</code>, <code>"WEAK"</code>,
     *                  <code>"STRONG"</code>
     */
    public void setMinEditAuthLevel(String authLevel) throws PortletException
    {
        if (authLevel == null)
        {
            throw new PortletException("Authentication level cannot be NULL");
        }
        authLevel = authLevel.trim();

        if (authLevel.equalsIgnoreCase("PUBLIC"))
        {
            mMinEditAuthLevel = ProviderUser.NOT_AUTHENTICATED;
        }
        else if (authLevel.equalsIgnoreCase("WEAK"))
        {
            mMinEditAuthLevel = ProviderUser.PUBLIC_WEAK_AUTH;
        }
        else if (authLevel.equalsIgnoreCase("STRONG"))
        {
            mMinEditAuthLevel = ProviderUser.PUBLIC_STRONG_AUTH;
        }
        else
        {
            throw new PortletException("Invalid authentication level: " + authLevel);
        }
    }


    /**
     * Returns the controller that will render this portlet's container. If
     * this has not been set with {@link #setContainerRenderer}, then the
     * provider's default container renderer, returned by {@link
     * ProviderDefinition#getContainerRenderer()} will be returned.
     *
     * The container renderer is responsible for rendering the portlet
     * container in each of the show modes the portlet supports.
     *
     * @return the portlet container renderer that should be used to render the
     * container for this portlet
     */
    public PortletContainerRenderer getContainerRenderer()
    {
        if (mContainerRenderer != null)
            return mContainerRenderer;

        // 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)
        {
            // portlet does not have a container renderer so see if we can get
            // one from the ProviderDefinition
            mContainerRenderer = mProviderDefinition.getContainerRenderer();
        
            // if we still do not have a container renderer, use the default.
            if (mContainerRenderer == null)
            {
                mContainerRenderer = new DefaultContainerRenderer();
            }
        }
        return mContainerRenderer;
    }


    /**
     * Sets the controller that will render this portlet's container.
     *
     * 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
     */
    public void setContainerRenderer(PortletContainerRenderer renderer)
        throws PortletException
    {
        mContainerRenderer = 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>PortletDefinition</code> a chance to initialize storage for the
     * portlet. This default implementation does nothing. Subclasses should
     * override.
     */
    public void register(ProviderInstance pi)
        throws IOException, AccessControlException
    {
    }


    /**
     * Called on each of a Provider instance's <code>PortletDefinition</code>s
     * when the provider is deregistered from a Portal. This gives a
     * <code>PortletDefinition</code> a chance to release storage for the
     * portlet. This default implementation does nothing. Subclasses should
     * override.
     */
    public void deregister(ProviderInstance pi)
        throws IOException, AccessControlException
    {
    }

    /**
     *  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>.
     *
     *  @param user the current user we are being asked to authorize.
     *  @return <code>true</code> is this user is authorized to access this
     *  portlet. This base implementation always returns
     *  <code>true</code>. Subclasses should override.
     */
    public boolean hasAccess(ProviderInstance pi,
                             ProviderUser user)
    {
        return true;
    }

    /**
    * Each portlet can specifiy the builder attached to edit this portlet.
    * This method returns the PortletBuilder associated with this portlet.
    * @return PortletBuilder the portlet builder attribute for this portlet
    * definition.
    */
    public BuilderMetaData getPortletBuilder()
    {
        return mBuilderMetaData;
    }

    /**
    * Each portlet can specifiy the builder attached to edit this portlet.
    * This method sets a PortletBuilder with this portletDefinition.
    * @param builderMetaData the PortletBuilder attribute for this Portlet
    * Defintion.
    */
    public void setPortletBuilder(BuilderMetaData builderMetaData)
    {
          mBuilderMetaData = builderMetaData;
    }    

    /**
     * Sets the locale personalization level for this {@link PortletDefinition}.
     * This level determines whether or not the portlet stores <i>localized</i>
     * default personalization data and if so, at what level. Possible values
     * are:
     * 
     * <P><UL>
     *   <li>{@link PortletConstants#PERSONALIZATION_LEVEL_NONE} - in this
     *   case, there will be no localized <i>default</i> personalization data.
     *   All edit default operations affect the same data set, irrespective of
     *   the user's locale.</li>
     *   <li>{@link PortletConstants#PERSONALIZATION_LEVEL_LANGUAGE} - in this
     *   case, localized <i>default</i> personalization data is stored and
     *   queried using the language code from the user's locale. For example,
     *   provided there are no user level customizations, users in locales
     *   &quot;en-US&quot; and &quot;en-GB&quot; will see the same localized
     *   defaults.</li>
     *   <li>{@link PortletConstants#PERSONALIZATION_LEVEL_LOCALE} - in this
     *   case, localized <i>default</i> personalization data is stored and
     *   queried using the language <i>and</i> country codes from the user's
     *   locale.</li>
     * </UL></P>
     * 
     * <P>Note: this setting does <i>not</i> affect user level personalizations,
     * just the default level.</P>
     * 
     * @param level the required locale personalization level.
     */
    public void setLocalePersonalizationLevel(int level)
    {
        mLocalePersonalizationLevel = level;
    }
    
    /**
     * Gets the locale personalization level for this {@link PortletDefinition}.
     * If the level has not been specified, the enclosing {@link
     * ProviderDefinition} is queried. See {@link
     * ProviderDefinition#getLocalePersonalizationLevel()}. For more information
     * on the locale personalization level, see {@link
     * #setLocalePersonalizationLevel(int)}.
     * 
     * @return the locale personalization level for this {@link
     * PortletDefinition}.
     */
    public int getLocalePersonalizationLevel()
    {
        if (mLocalePersonalizationLevel !=
            PortletConstants.PERSONALIZATION_LEVEL_UNSPECIFIED)
        {
            return mLocalePersonalizationLevel;
        }
        else
        {
            return mProviderDefinition.getLocalePersonalizationLevel();
        }
    }

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

        // Check the validity of the member variables
        if (mInstanceClass == null)
        {
            errors.addError(ctx, "portlet class must not be null", null);
        }
        if (mProviderDefinition == null)
        {
            errors.addError(ctx, "Provider Definition must not be null", null);
        }
        if (mPortletId < 1)
        {
            errors.addError(ctx, "Portlet Id must be > 0", "id = " +
                            mPortletId);
        }
        if (mAcceptContentTypes == null || mAcceptContentTypes.isEmpty())
        {
            errors.addError(ctx, "acceptContentTypes must be set", null);
        }
        if (mMinEditAuthLevel != ProviderUser.NOT_AUTHENTICATED &&
            mMinEditAuthLevel != ProviderUser.PUBLIC_WEAK_AUTH &&
            mMinEditAuthLevel != ProviderUser.USER_WEAK_AUTH &&
            mMinEditAuthLevel != ProviderUser.PUBLIC_STRONG_AUTH &&
            mMinEditAuthLevel != ProviderUser.USER_STRONG_AUTH)
        {
            errors.addError(ctx, "minEditAuthLevel is not a valid value",
                       "minEditAuthLevel = " + mMinEditAuthLevel);
        }
        if(mRewriteUrls < -1 || mRewriteUrls > 1)
        {
            errors.addError(ctx,
                       "rewriteUrls must be either unspecified, " +
                       "false or true (-1, 0 or 1)",
                       "rewriteUrls = " + mRewriteUrls);
        }

        // Validate the InputParameters.
        Iterator it = null;
        ParameterDefinition pd = null;
        String name = null;
        Map names = new HashMap(5);
        if (mInputParameters != null)
        {
            it = mInputParameters.iterator();
            while (it.hasNext())
            {
                pd = (ParameterDefinition)it.next();
                if (pd instanceof Validateable)
                {
                    ((Validateable)pd).validate(ctx, errors);
                }
                name = pd.getName();
                if (names.containsKey(name))
                {
                    errors.addError(ctx, "InputParameter name not unique",
                                    name);
                }
                else
                {
                    names.put(name, null);
                }
            }
            names.clear();
        }

        // Validate the EventDefinitions.
        EventDefinition ed = null;
        if (mEvents != null)
        {
            it = mEvents.iterator();
            while (it.hasNext())
            {
                ed = (EventDefinition)it.next();
                if (ed instanceof Validateable)
                {
                    ((Validateable)ed).validate(ctx, errors);
                }
                name = ed.getName();
                if (names.containsKey(name))
                {
                    errors.addError(ctx, "EventDefinition name not unique",
                                    name);
                }
                else
                {
                    names.put(name, null);
                }
            }
            names.clear();
        }

        // Check that accept content types are unique within a portlet
        if (mAcceptContentTypes != null && !mAcceptContentTypes.isEmpty())
        {
            it = mAcceptContentTypes.iterator();
            while (it.hasNext())
            {
                name = (String)it.next();
                if (names.containsKey(name))
                {
                    errors.addError(ctx,
                                    "Duplicate Accept Content Types found",
                                    "acceptContentType = " + name);
                }
                else
                {
                    names.put(name, null);
                }
            }
        }
    }

    protected String getPortletName(PortletDefinition p)
    {
        String name = p.getName(p.getDefaultLocale());
        if (name != null)
        {
            name = "Portlet " + name;
        }
        else
        {
            List portlets = mProviderDefinition.getPortletDefinitions();
            int index = portlets.indexOf(p);
            // Assume will be the next in the list if it is not already in
            // there
            if(index == -1)
            {
                index = portlets.size();
            }
            name = "Portlet #" + index;
        }
        return name;
    }

    /**
     * Encapsulates the logic to find a string which might be translated.
     *
     * @param string  member variable which might be set or null 
     * @param userKey  user defined key which might be set or null
     * @param constantKey  defined constant key for resource bundle lookup
     * @param l  Locale to get string for
     *
     * @return the proper string, if necessary from the resource bundle
     */
    private String findTranslatedString(String string, String userKey,
                                        String constantKey, Locale l)
    {
        // always return passed in string if set
        if (string != null)
        {
            return string;
        }
        else
        {
            ResourceBundle b = getResourceBundle(l);
            if (b != null)
            {
                // if there is a user specified key, use that
                // otherwise, use default
                String key = (userKey != null) ? userKey : constantKey;

                // get string from bundle.
                try
                {
                    return b.getString(key);
                }
                catch (MissingResourceException mre)
                {
                    // If none present, null is returned.
                }
            }
        }
        return null;
    }

}
