/* Copyright (c) 2001, Oracle Corporation. All rights reserved. */
package oracle.portal.provider.v2;

import java.util.*;
import oracle.portal.provider.v2.preference.PreferenceStore;
import oracle.portal.provider.v2.preference.PreferenceStoreException;
import oracle.portal.provider.v2.render.PortletContainerRenderer;
import oracle.portal.provider.v2.render.DefaultContainerRenderer;
import oracle.portal.provider.v2.render.ContainerRenderer306;
import oracle.portal.provider.v2.url.UrlUtils;
import oracle.portal.provider.v2.Validateable;
import oracle.portal.provider.v2.utils.ValidationError;
import oracle.portal.provider.v2.oid.OidManager;
import oracle.portal.log.LogManager;
import oracle.webdb.provider.v2.builder.BuilderMetaData;

/**
 * A <code>ProviderDefinition</code> is a 'blueprint' for a provider. It is
 * simply a container for meta data that drives the behaviour of one or more
 * {@link ProviderInstance} objects.<p>
 *
 * This base class follows basic JavaBeans design patterns in order to expose
 * the properties required by every <code>ProviderDefinition</code>. This
 * allows a <code>ProviderDefinition</code>'s initialization to be managed by a
 * generic mechanism (a {@link ProviderLoader}) and gives scope for provider
 * meta data to be loaded from a variety of storage mechanisms (e.g. XML file
 * or database). Users who require a richer set of provider-level properties or
 * accessors to these properties should feel free to use their own subclass.
 */
public class ProviderDefinition implements Validateable
{
    protected Class mInstanceClass = ProviderInstance.class;
    protected ProviderContext mProviderContext;
    protected boolean mUseOldStyleHeaders = false;
    protected boolean mEnableSession = true;
    protected List mPortletDefinitions = new ArrayList(10);
    protected Map mPreferenceStores = new HashMap();
    protected PreferenceStore mDefaultPreferenceStore;
    
    private   boolean mPassAllUrlParams;

    /**
     * mCacheLevel stores the level of caching for the provider.
     *
     * The portlet setting for cache level takes precedence over the provider
     * setting.
     */
    private   int mCacheLevel  = PortletConstants.CACHE_LEVEL_UNSPECIFIED;

    /**
     * mRewriteUrls stores whether or not URL rewriting will be performed
     * on the portlet's rendered output.
     *
     * The portlet setting for rewrite URLs takes precedence over the provider
     * setting.
     */
    private   boolean mRewriteUrls = false;

    private   PortletContainerRenderer mDefaultContainerRenderer;
    private   Object mLock = new Object();

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

    /**
     * mOidManager stores the OID Manager object which provides access
     * to users details. This value may be null if the OID manager is not
     * configured.
     */
    protected OidManager mOidManager = null;
    private boolean mHasOidLoaded = false;

    /**
     * Should be called immediately after construction of a
     * <code>ProviderDefinition</code> to associate it with a shared {@link
     * ProviderContext} object describing its deployment
     * environment. Generally, all providers running in the same environment
     * (e.g. within the same adapter servlet) share the same
     * <code>ProviderContext</code>.
     * @param pc an object describing the deployment environment of the
     * provider.
     */
    public void init(ProviderContext pc) {
        mProviderContext = pc;
    }

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

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

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

    /**
     * Controls whether the <code>ProviderInstance</code> starts a (servlet)
     * session in its {@link ProviderInstance#initSession} method.
     * @param session <code>true</code> if the ProviderInstance is to start a
     * session in <code>initSession</code>, or <code>false</code> otherwise.
     */
    public void setHasSession(boolean session)
    {
        mEnableSession = session;
    }

    /**
     * Determines whether the <code>ProviderInstance</code> will start a (servlet)
     * session in its {@link ProviderInstance#initSession} method.
     * @return <code>true</code> if the ProviderInstance will start a session
     * in <code>initSession</code>, or <code>false</code> otherwise.
     */
    public boolean getHasSession()
    {
        return mEnableSession;
    }

    /**
     * Controls whether Portal will pass the requested page's URL parameters to
     * the provider's portlets by default.<p>
     *
     * @param value boolean indicating whether Portal will pass the requested
     * page's URL parameters to portlets by default.
     *
     * <ul><li>If <code>true</code>, then each portlet whose definition doesn't
     *         explicitly override this setting will receive all the
     *         (non-system) parameters from the page URL.</li>
     *
     *     <li>If <code>false</code> (the default), then each portlet which
     *         declares input parameters and doesn't explicitly override this
     *         setting (with {@link PortletDefinition#setPassAllUrlParams})
     *         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 = value;
    }

    /**
     * Sets the caching level used for portlet render requests for 
     * portlets belonging to this provider.<p>
     *
     * @param cacheLevel int indicates the caching level - should 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 ProviderException
    {
        if (cacheLevel < PortletConstants.CACHE_LEVEL_SYSTEM ||
            cacheLevel > PortletConstants.CACHE_LEVEL_SESSION)
        {
            throw new ProviderException("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 portlet render requests for portlets belonging to this provider.<p>
     *
     * When <code>true</code>, this tag enables URL rewriting to improve 
     * portlet caching.
     * 
     * URL Rewriting can be specified at both the provider and portlet 
     * levels. The order of precedence is as follows:
     * <ul>
     *   <li>If specified, the portlet setting is used.</li>
     *   <li>If specified, the provider setting is used.</li>
     *   <li>Otherwise, URL rewriting is not performed.</li>
     * </ul>
     *
     * <p>In other words, the provider URL rewriting setting will be used 
     * by those of its portlets that have not specified their own setting 
     * by calling {@link PortletDefinition#setRewriteUrls}.
     * The default provider setting is <code>false</code>.
     *
     * <p>URL rewriting is a technique that allows you to create links or 
     * HTML forms without Portal having to send the base URL to the 
     * web provider. This means that the URLs, which can vary alot, do not 
     * get used as part of the cache key for your portlet. In most cases, 
     * this will increase the effectiveness of portlet caching. 
     *
     * <p> For example, you might want to create a link based on the Page URL,
     * but with your own parameters appended to it. If URL rewriting is 
     * disabled, the base URL is sent to the web provider, the PDK 
     * framework appends your parameters to the base URL and then you 
     * include the resulting URL in your portlet.   
     *
     * <p> If URL rewriting is enabled, the base URL is never sent to the 
     * web provider. Instead, when you call the methods to create URLs 
     * such as {@link UrlUtils#constructHTMLLink},
     * the framework constructs a specially formatted 
     * String that you embed in your portlet. This String is then processed 
     * by Portal to create the same URL that would have been constructed 
     * if URL rewriting was disabled.
     *
     * <p>In order to enable URL rewriting you may need to make some small 
     * changes to your portlet code. You will need to make changes if you are 
     * submitting data via an HTML form or if you are appending encoded 
     * parameters to a simple link.
     *
     * <p> In the case of HTML forms, you should use the 
     * {@link UrlUtils#htmlFormName}
     * method to construct a qualified form name that is unique to the 
     * current portlet instance.
     *
     * <p> In the case of simple links, you should use 
     * <code>UrlUtils.constructHTMLLink</code>. This method constructs an entire
     * HTML &lt;a&gt;..&lt;/a&gt; tag sequence embedding your link within it.
     *
     * @param rewriteUrls boolean true if URL rewriting is to be performed.
     *        Otherwise false.
     */
    public void setRewriteUrls(boolean rewriteUrls)
    {
        mRewriteUrls = rewriteUrls;
    }



    /**
     * Determines whether Portal will pass the requested page's URL parameters
     * to the provider's portlets by default.
     * @return boolean indicating whether Portal will pass a requested page's
     * URL parameters to portlets by default.
     * @see #setPassAllUrlParams
     */
    public boolean getPassAllUrlParams()
    {
        return mPassAllUrlParams;
    }

    /**
     * Returns the caching level for portlets belonging to this provider.<p>
     *
     * The portlet setting for cache level takes precedence over the provider
     * setting.
     *
     * @return int the provider's cache level.
     *
     * @see #setCacheLevel
     */
    public int getCacheLevel()
    {
        return mCacheLevel;
    }

    /**
     * Determines whether URL rewriting will be performed for portlets
     * belonging to this provider.
     *
     * The portlet setting for rewrite URLs takes precedence over the provider
     * setting.
     *
     * @return boolean true if URL rewriting is to be performed. 
     *         Otherwise false.
     *
     * @see #setRewriteUrls
     */
    public boolean getRewriteUrls()
    {
        return mRewriteUrls;
    }

    /**
     * Returns the list of associated {@link PortletDefinition} objects
     * containing the portlet-level meta data for each of the provider's
     * portlets.
     * @return a list of <code>ProviderDefinition</code>s containing the meta
     * data for each of the provider's portlets.
     */
    public List getPortletDefinitions()
    {
        return mPortletDefinitions;
    }

    /**
     * Associates with the provider a <code>PortletDefinition</code> object
     * containing the meta data for one of the portlets it owns. Note that each
     * <code>PortletDefinition</code> added should have a unique id.
     * @param pd a <code>ProviderDefinition</code> containing the portlet-level
     * meta data for one of the provider's portlets.
     */
    public void addPortletDefinition(PortletDefinition pd)
    {
        mPortletDefinitions.add(pd);
    }

    /**
     * Retrieves the <code>PortletDefinition</code> object containing the meta
     * data for the portlet with the given id.
     * @param id a <code>long</code> identifying the portlet within the
     * provider
     * @return a <code>ProviderDefinition</code> containing the portlet-level
     * meta data for the portlet with the given id, or <code>null</code> if no
     * such portlet has been defined.
     */
    public PortletDefinition getPortletDefinition(long id)
    {
        List portletDefinitions = getPortletDefinitions();
        Iterator portletDefinitionIter = portletDefinitions.iterator();
        while( portletDefinitionIter.hasNext())
        {
            PortletDefinition portletDefinition = (PortletDefinition)portletDefinitionIter.next();
            if (portletDefinition.getId() == id)
            {
                return portletDefinition;
            }
        }
        return null;
    }

    /**
     * Adds to the set of <code>PreferenceStore</code> objects that may be used
     * by the provider for persistent storage. Note that each
     * <code>PreferenceStore</code> added should have a unique identifying
     * name.
     *
     * @param ps a <code>PreferenceStore</code>.
     */
    public void addPreferenceStore(PreferenceStore ps)
    {
        // Ensure no pref store exists with the same name.
        String storeName = ps.getName();
        if (mPreferenceStores.containsKey(storeName))
        {
            throw new IllegalArgumentException("A PreferenceStore with name: " +
                                               storeName + " already exists.");
        }
        mPreferenceStores.put(ps.getName(), ps);
        if(mDefaultPreferenceStore == null)
        {
            mDefaultPreferenceStore = ps;
        }
    }

    /**
     * Selects the default {@link PreferenceStore} that should be used by the
     * provider and all its child objects for which a preference store has not
     * been chosen explicitly.
     * @param preferenceStoreName the name of one of the provider's
     * <code>PreferenceStore</code>s, which should have been added with {@link
     * #addPreferenceStore}.
     * @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 = getPreferenceStore(preferenceStoreName);
    }

    /**
     * Gets the default <code>PreferenceStore</code> that should be used by the
     * provider 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 first <code>PreferenceStore</code> to be
     * associated with {@link #addPreferenceStore} will be returned; otherwise
     * <code>null</code> will be returned.
     * @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()
    {
        return mDefaultPreferenceStore;
    }

    /**
     * Retrieves the <code>PreferenceStore</code> with the given name.
     * @param preferenceStoreName name of the <code>PreferenceStore</code> to
     * be retrieved, which should have been added with {@link
     * #addPreferenceStore}.
     * @exception PreferenceStoreException if a <code>PreferenceStore</code>
     * with the given name has not been defined.
     */
    public PreferenceStore getPreferenceStore(String preferenceStoreName)
        throws PreferenceStoreException
    {
        PreferenceStore ps = (PreferenceStore)mPreferenceStores.get(preferenceStoreName);
        if(ps == null)
        {
            throw new PreferenceStoreException("No such preference store \"" +
                                               preferenceStoreName + "\"");
        }
        return ps;
    }



    /**
     * Specifies whether or not {@link
     * oracle.portal.provider.v2.render.PortletRendererUtil} should render
     * headers and footers for this provider's portlets in the style used by a
     * 3.0.6 portal or earlier, rather than the style used by a post-3.0.6
     * portal.
     *
     * @param useOldStyleHeaders boolean indicating which style of form headers
     * and footers should be rendered by
     * <code>PortletRendererUtil</code>.<code>true</code> - render portal 3.0.6
     * style headers and footers. <code>false</code> - render new style headers
     * and footers.
     *
     * @deprecated use {@link #setContainerRenderer} with {@link
     * oracle.portal.provider.v2.render.ContainerRenderer306} to get equivalent
     * behavior. Alternatively, create your own container renderer by extending
     * {@link PortletContainerRenderer}
     */
    public void setUseOldStyleHeaders(boolean useOldStyleHeaders)
    {
        mUseOldStyleHeaders = useOldStyleHeaders;
    }


    /**
     * Signals whether {@link
     * oracle.portal.provider.v2.render.PortletRendererUtil} will render
     * headers and footers for this provider's portlets in the style used by a
     * 3.0.6 portal or earlier, rather than the style used by a post-3.0.6
     * portal.
     *
     * @return boolean indicating which style of form headers and footers
     * should be rendered by <code>PortletRendererUtil</code>.<code>true</code>
     * - render portal 3.0.6 style headers and footers. <code>false</code> -
     * render new style headers and footers.
     *
     * @deprecated use {@link #getContainerRenderer} to determine the default
     * container renderer that should be used to render portlet containers for
     * this provider
     */
    public boolean getUseOldStyleHeaders()
    {
        return mUseOldStyleHeaders;
    }


    /**
     * Returns the default portlet container renderer.<p>
     *
     * The portlet container renderer is responsible for rendering the portlet
     * container in each of the show modes the portlet supports.<p>
     *
     * Each portlet can specify its own container renderer and you can specify
     * a default at the provider level to be used by any portlet that does not
     * explicitly specify it's own container renderer.
     *
     * @return the default portlet container renderer. Note that if a default
     * container renderer has not been specified with {@link
     * #setContainerRenderer}, then a {@link DefaultContainerRenderer} object
     * will ve returned.
     */
    public PortletContainerRenderer getContainerRenderer()
    {
        if (mDefaultContainerRenderer != null)
            return mDefaultContainerRenderer;

        // 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)
        {
            // a default container renderer was not specified when the
            // provider was loaded - fall back to DefaultContainerRenderer
            LogManager.config("Default container renderer not specified");
            if (getUseOldStyleHeaders())
            {
                LogManager.config("useOldStyleHeaders = true: using ContainerRenderer306");
                mDefaultContainerRenderer = new ContainerRenderer306();
            }
            else
            {        
                LogManager.config("defaulting container renderer to DefaultContainerRenderer");
                mDefaultContainerRenderer = new DefaultContainerRenderer();
            }
        }
        return mDefaultContainerRenderer;
    }


    /**
     * Sets the default portlet container renderer.<p>
     *
     * The container renderer is responsible for rendering the portlet
     * container in each of the show modes the portlet supports.<p>
     *
     * Each portlet can specify it's own container renderer and you can specify
     * a default at the provider level to be used by any portlet that does not
     * explicitly specify it's own container renderer.
     *
     * @param renderer the portlet container renderer
     */
    public void setContainerRenderer(PortletContainerRenderer renderer)
    {
        mDefaultContainerRenderer = renderer;
    }

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

    /**
    * Each provider can specifiy the builder attached to edit this provider.
    * This method sets a ProviderBuilder with this providerdefintion.
    * @param builderMetaData the ProviderBuilder attribute for this Provider
    * Defintion.
    */
    public void setProviderBuilder(BuilderMetaData builderMetaData) 
    {
          mBuilderMetaData = builderMetaData;
    }    

    /**
     * Validates the internal state of this <code>ProviderDefinition</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)
    {
        // Update the context string.
        String ctx = 
            ValidationError.newContext(context, "ProviderDefinition", null);
       
        // Check the validity of the member variables
        if ( mInstanceClass == null )
        {
            errors.addError(ctx, "instance class must be set", "");
        }
        if ( mProviderContext == null )
        {
            errors.addError(ctx, "Provider Context must be set", "");
        }
        if (mCacheLevel < PortletConstants.CACHE_LEVEL_UNSPECIFIED ||
            mCacheLevel > PortletConstants.CACHE_LEVEL_SESSION)
        {
            errors.addError(ctx,
                            "Invalid cacheLevel value",
                            "cacheLevel = " + mCacheLevel);
        }

        // Validate contained PortletDefinitions
        Iterator it = getPortletDefinitions().iterator();
        Map idMap = new HashMap(10);
        Map localeMap = new HashMap(10);
        Map nameMap = null;
        String id = null;
        String locale = null;
        String portletName = null;
        while (it.hasNext())
        {
            PortletDefinition pd = (PortletDefinition)it.next();
            if (pd instanceof Validateable)
            {
                ((Validateable)pd).validate(ctx, errors);
            }
            locale = pd.getDefaultLocale().toString();
            id = String.valueOf(pd.getId());
            portletName = pd.getName(pd.getDefaultLocale());
            if (idMap.containsKey(id))
            {
                errors.addError(ctx, "Portlet id not unique", id);
            }
            else
            {
                idMap.put(id, null);
            }
            nameMap = (Map)localeMap.get(locale);
            if (nameMap == null)
            {
                nameMap = new HashMap(5);
                localeMap.put(locale, nameMap);
            }
            if (nameMap.containsKey(portletName))
            {
                errors.addError(ctx, "Portlet name not unique", portletName);
            }
            else
            {
                nameMap.put(portletName, null);
            }
        }

        // Validate contained PreferenceStores
        it = mPreferenceStores.entrySet().iterator();
        PreferenceStore ps = null;
        while (it.hasNext())
        {
            ps = (PreferenceStore)((Map.Entry)it.next()).getValue();
            if (ps instanceof Validateable)
            {
                ((Validateable)ps).validate(ctx, errors);
            }
        }
    }


    /**
     * Gets the OidManager. The OidManager provides access to user details.
     * @return the OidManager or null depeneding on whether the OidManager   
     * has been configured.
     */
    public OidManager getOidManager() throws ProviderException
    {
        if(mOidManager != null || mHasOidLoaded)
        {
            return mOidManager;
        }

        mHasOidLoaded = true;

        // Load the class details from Deployment properties

        // Check if there is any configuration data so that an 
        // Oid manager can be created.
        String oidManager = (String)
            mProviderContext.getAttribute(
                OidManager.DEPLOYMENT_PARAM_OID_MANAGER);

        if(oidManager != null)
        {
            oidManager = oidManager.trim();
            if(oidManager.equals("true"))
            {
                mOidManager = new OidManager();
                mOidManager.init(mProviderContext);
            }
        }
        return mOidManager;
    }

}
