/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000-2001 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" and
 *     "Apache Jetspeed" must not be used to endorse or promote products
 *    derived from this software without prior written permission. For
 *    written permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache" or
 *    "Apache Jetspeed", nor may "Apache" appear in their name, without
 *    prior written permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

package org.apache.jetspeed.util.template;

import org.apache.ecs.ConcreteElement;
import org.apache.ecs.StringElement;
import org.apache.turbine.util.Log;
import org.apache.turbine.util.RunData;
import org.apache.turbine.services.pull.ApplicationTool;
import org.apache.jetspeed.portal.Portlet;
import org.apache.jetspeed.portal.PortletSet;
import org.apache.jetspeed.portal.PortletControl;
import org.apache.jetspeed.services.PortalToolkit;
import org.apache.jetspeed.services.PortletFactory;
import org.apache.jetspeed.services.PsmlManager;
import org.apache.jetspeed.services.resources.JetspeedResources;
import org.apache.jetspeed.services.Profiler;
import org.apache.jetspeed.services.rundata.JetspeedRunData;
import org.apache.jetspeed.om.SecurityReference;
import org.apache.jetspeed.om.profile.PSMLDocument;
import org.apache.jetspeed.om.profile.Profile;
import org.apache.jetspeed.om.profile.ProfileException;
import org.apache.jetspeed.om.profile.Entry;
import org.apache.jetspeed.modules.ParameterLoader;
import org.apache.jetspeed.services.security.PortalResource;
import org.apache.jetspeed.om.registry.Parameter;
import org.apache.jetspeed.om.registry.PortletEntry;
import org.apache.jetspeed.om.security.JetspeedUser;
import org.apache.jetspeed.services.Registry;
import org.apache.jetspeed.services.JetspeedSecurity;
import org.apache.jetspeed.portal.security.portlets.PortletWrapper;

import java.util.Enumeration;
import java.util.Stack;
import java.util.Map;

/**
 * Utility class for accessing Jetspeed in a "pull" mode
 *
 * <strong>Since the tool stores a RunData object, it may not be
 * shared between threads and/or requests</strong>
 *
 * @author <a href="mailto:raphael@apache.org">Raphaël Luta</a>
 * @author <a href="mailto:taylor@apache.org">David Sean Taylor</a>
 * @author <a href="mark_orciuch@ngsltd.com">Mark Orciuch</a>
 * @author <a href="mailto:weaver@apache.org">Scott T. Weaver</a>
 *
 * @version $Id: JetspeedTool.java,v 1.31 2003/04/11 15:29:57 weaver Exp $
 */
public class JetspeedTool implements ApplicationTool
{
    /** RunData object for this request */
    protected JetspeedRunData rundata = null;
    
    /** Empty constructor used by introspection
     */
    public JetspeedTool()
    {
    }

    /** The Tool constructor
     *
     * @param data the RunData object for the current request
     */
    public JetspeedTool(RunData data)
    {
        this.rundata = (JetspeedRunData)data;
    }
    
    /**
     * This will initialise a JetspeedTool object that was
     * constructed with the default constructor (ApplicationTool
     * method).
     *
     * @param data assumed to be a RunData object
     */
    public void init(Object data)
    {
        this.rundata = (JetspeedRunData)data;
    }

    /**
     * Refresh method - does nothing
     */
    public void refresh()
    {
        // empty
    }

    /** 
     * Returns the portlet content customized for the current user.
     * Currently, the layout engine does not handle panes, so the 
     * panes are mapped to real PSML files.
     * If the pane name is null or "default", the profiler will automatically
     * chose the PSML content, else the tool will try to load the PSML
     * file with the specified name
     *
     * @param name the name of the pane to render
     * @return the rendered content of the pane
     */
    public ConcreteElement getPane(String name)
    {
        ConcreteElement result = null;
        String msg = "";
        
        if (null != rundata)
        {
            Profile profile = rundata.getProfile();
            try
            {
                if (null == profile)
                {
                    profile = Profiler.getProfile(rundata);
                    if (profile == null)
                    {
                        throw new ProfileException("Profile not found.");
                    }
                    rundata.setProfile(profile);
                }
            
                if ((name == null) ||  Profiler.DEFAULT_PROFILE.equals(name) || "".equals(name))
                    ;
                else
                    profile.setName(name);
     
                PSMLDocument doc = profile.getDocument();
                if (null != doc)
                {
                    result = PortalToolkit.getSet(doc.getPortlets()).getContent(rundata);
                }
            }
            catch (Exception e)
            {
                Log.warn("JetspeedTool.getPane: problem getting: "
                            + name + " from current request's profile: " + e.toString());
                msg = e.getMessage();
            }

        }
        
        if (result == null)
        {
            result = new StringElement("Error retrieving Portal Page: " + msg);
        }

        return result;
    }

    /**
     * Return the content of a named portlet. This portlet is sought in
     * the current PSML resource.
     *
     * If a control is attached to the portlet description, returns the defined
     * portlet and control, otherwise use the default control.
     *
     * Note:  This will return the FIRST portlet with a name = name.  Use getPortletById().
     *
     * @param name the name of the portlet to render
     * @return the rendered content of the portlet
     *
     * @deprecated Use getPortletById()
     */
    public ConcreteElement getPortlet(String name)
    {
        ConcreteElement result = null;
        Portlet found = null;
        Stack sets = new Stack();
        sets.push(rundata.getProfile().getRootSet());
        
        while ((sets.size() > 0) && (found==null))
        {
            PortletSet set = (PortletSet)sets.pop();
            
            if (set.getName().equals(name))
            {
                found = set;
            }
            else
            {
                Enumeration en = set.getPortlets();
                while((found==null) && en.hasMoreElements())
                {
                    Portlet p = (Portlet)en.nextElement();
                        
                    // unstack the controls to find the real PortletSets
                    Portlet real = p;
                    while (real instanceof PortletControl)
                    {
                        real = ((PortletControl)p).getPortlet();
                    }
                        
                    if (real instanceof PortletSet)
                    {
                        // we'll explore this set afterwards
                        sets.push(real);
                    }
                    else if (p.getName().equals(name))
                    {                        
                        found = p;
                    }
                }
            }
        }
        
        if (found!=null)
        {
            result = found.getContent(rundata);
        }
        
        if (result==null)
        {
            //the customizer already streamed its content, return a stub
            result = new ConcreteElement();
        }

        return result;
    }

    /** This method retrieves the appropriate customizer portlet for the 
     *  current portlet
     *
     *  @param p the portlet to customize
     *  @param data the RunData for this request
     *  @return the portlet object of the appropriate customizer
     */
    public static Portlet getCustomizer(Portlet p)
    {
        Portlet customizer = p;

        while (p instanceof PortletControl)
        {
            p = ((PortletControl)p).getPortlet();
        }
        
        // if the portlet cannot customize itself...
        if ( !p.providesCustomization() )
        {

            //look for the customizer name in the portlet
            //config (from Registry definition)
        
            String name = p.getPortletConfig().getInitParameter("_customizer");
            
            if (name == null)
            {
                String key = (p instanceof PortletSet)?"PortletSet":"Portlet";            

                name = JetspeedResources.getString("customizer."+key,key+"Customizer");
            }
        
            try
            {
                customizer = PortletFactory.getPortlet(name, p.getID()+"customize");
                customizer.getPortletConfig()
                          .setPortletSkin(p.getPortletConfig().getPortletSkin());
                PortletControl control = PortalToolkit.getControl((String)null);
                if (control!=null)
                {
                    control.setPortlet(customizer);
                    control.init();
                    return control;
                }
            }
            catch (Exception e)
            {
                Log.error(e);
            }
        }
            
        return customizer;
    }

    /** This method retrieves the appropriate information portlet for the 
      *  current portlet
      *
      *  @param p the portlet to display information about
      *  @param data the RunData for this request
      *  @return the portlet object of the appropriate customizer
      */
     public static Portlet getPortletInfoPortlet(RunData data)
     {
         Portlet info = null;
         
         String name = JetspeedResources.getString("PortletInfoPortlet.name", "PortletInfoPortlet");
 
         try {
 
             if (null != data)
             {
                 JetspeedRunData jdata = (JetspeedRunData)data;
                 Profile profile = jdata.getProfile();
 
                 if (null == profile)
                 {
                     Log.warn("JetspeedTool: profile is null");
                     profile = Profiler.getProfile(jdata);
                     jdata.setProfile(profile);
                 }
 
                 Portlet source = findPortlet(data);
                 if (source != null) {
                     jdata.setPortlet(source.getName());
                     info = PortletFactory.getPortlet(name, "PortletInfoPortlet");
                     info.getPortletConfig().setPortletSkin(source.getPortletConfig().getPortletSkin());
                     PortletControl control = PortalToolkit.getControl((String)null);
                     if (control != null) 
                     {
                         control.setPortlet(info);
                         control.init();
                         return control;
                     }
                 }
             }
         } catch (Exception e) {
             Log.error(e);
         }
             
         return info;
     }
 
     /**
      * Finds portlet identified by js_peid in the current user's profile
      * 
      * @param rundata for this request
      * @return portlet identified by js_peid
      */
     private static Portlet findPortlet(RunData rundata) {
 
         Portlet found = null;
         JetspeedRunData jdata = (JetspeedRunData)rundata;
         String peid = jdata.getJs_peid();
         if (peid != null) 
         {
             Stack sets = new Stack();
             sets.push(jdata.getProfile().getRootSet());
 
             while ((found==null) && (sets.size() > 0))
             {
                 PortletSet set = (PortletSet)sets.pop();
 
                 if (set.getID().equals(peid))
                 {
                     found = set;
                 }
                 else
                 {
                     Enumeration en = set.getPortlets();
                     while((found==null) && en.hasMoreElements())
                     {
                         Portlet p = (Portlet)en.nextElement();
 
                         // unstack the controls to find the real PortletSets
                         Portlet real = p;
                         while (real instanceof PortletControl)
                         {
                             real = ((PortletControl)p).getPortlet();
                         }
 
                         if (real instanceof PortletSet)
                         {
                             if (real.getID().equals(peid))
                             {
                                 found=real;
                             }
                             else
                             {
                                 // we'll explore this set afterwards
                                 sets.push(real);
                             }
                         }
                         else if (p.getID().equals(peid))
                         {                        
                             found = p;
                         }
                     }
                 }
             }
         }
 
         return found;
     }


    /** 
     * Return the content of a portal element given the id of the element. 
     *
     * @param id The portlet id
     * @return the rendered content of the portlet
     */
    public ConcreteElement getPortalElement(String id)
    {
        ConcreteElement result = null;
        
        if (null != rundata)
        {
            Profile profile = rundata.getProfile();
            try
            {
                if (null == profile)
                {
                    System.out.println("profile is null");
                    profile = Profiler.getProfile(rundata);
                    rundata.setProfile(profile);
                }
 
                PSMLDocument doc = profile.getDocument();
                if (null != doc)
                {                
                    Entry entry = doc.getEntryById(id);
                    if (null == entry)
                    {
                        // FIXME: need to write this function
                        // Portlets ps = doc.getPortletsById(id);
                        result = new  StringElement("not implemented - PortletElement");
                    }
                    else
                    {
                        Portlet p = PortletFactory.getPortlet( entry );
                        if (p != null)
                        {
                            result = p.getContent(rundata);
                        }
                        else
                           result = new  StringElement("Error retrieving PortletElement");

                    }
                }                
            }
            catch (Exception e)
            {
                Log.error(e);
            }
        }
        
        if (result == null)
        {
            result = new StringElement("Error fetching pane");
        }

        return result;

    }

    /**
     * Return the content of a portlet using the portlet's id (PEID). This portlet is sought in
     * the current PSML resource.
     *
     * If a control is attached to the portlet description, returns the defined
     * portlet and control, otherwise use the default control.
     *
     * @param peid the peid of the portlet to render
     * @return the rendered content of the portlet
     */
    public ConcreteElement getPortletById(String peid)
    {
        ConcreteElement result = null;
        Portlet found = null;
        Stack sets = new Stack();
        sets.push(rundata.getProfile().getRootSet());
        
        while ((sets.size() > 0) && (found==null))
        {
            PortletSet set = (PortletSet)sets.pop();
            
            if (set.getID().equals(peid))
            {
                found = set;
            }
            else
            {
                Enumeration en = set.getPortlets();
                while((found==null) && en.hasMoreElements())
                {
                    Portlet p = (Portlet)en.nextElement();
                        
                    // unstack the controls to find the real PortletSets
                    Portlet real = p;
                    while (real instanceof PortletControl)
                    {
                        real = ((PortletControl)p).getPortlet();
                    }
                        
                    if (real instanceof PortletSet)
                    {
                        // we'll explore this set afterwards
                        sets.push(real);
                    }
                    else if (p.getID().equals(peid))
                    {                        
                        found = p;
                    }
                }
            }
        }
        
        if (found!=null)
        {
            // Return portlet's content checking the security first
            result = PortletWrapper.wrap(found).getContent(rundata);
        }
        
        if (result==null)
        {
            //the customizer already streamed its content, return a stub
            result = new ConcreteElement();
        }

        return result;
    }

    /**
     * Return the content of a portlet using the portlet's name. This portlet is sought in
     * the registry. This is useful when you want to get portlet's content without
     * actually having the portlet in user's profile (for example, to preview a portlet
     * before adding it to the profile).
     * <P>
     * If a control name is specified to the portlet description, returns the defined
     * portlet and control, otherwise use the default control.
     * <P>
     * Issues to resolve:
     * <UL>
     * <LI>is new portlet instance created everytime someone previews the same portlet?</LI>
     * <LI>should use the same skin as the current pane</LI>
     * <LI>if TitlePortletControl is used, the action icons (max, min, etc) are not functional.
     * Also, customize icon should not be present.</LI>
     * <LI> interactive portlets (such as DatabaseBrowser) lose functionality (such as sorting
     * in DatabaseBrowser).</LI>
     * </UL>
     * 
     * @param portletName
     *               Name of the portlet as defined in registry
     * @param controlName
     *               Optional control name to use in displaying the portlet
     * @return the rendered content of the portlet
     */
    public ConcreteElement getPortletFromRegistry(String portletName, String controlName)
    {

        ConcreteElement result = null;
        Portlet p = null;
        
        try 
        {
            // Always set portlet id to "preview" so each preview request gets it from the cache.
            // At least, I think that's how it works.
            p = PortletFactory.getPortlet(portletName, "preview");
            PortletControl control = controlName == null ? PortalToolkit.getControl((String) null) 
                                                         : PortalToolkit.getControl(controlName);
            if (control != null)
            {
                JetspeedRunData jdata = (JetspeedRunData) rundata;
                // Use the profile's skin
                p.getPortletConfig().setPortletSkin(PortalToolkit.getSkin(jdata.getProfile().getDocument().getPortlets().getSkin()));
                control.setPortlet(p);
                control.init();
                result = control.getContent(rundata);
            } 
            else if (p != null)
            {
                result = p.getContent(rundata);
            }
        } 
        catch (Exception e) 
        {
            Log.error(e);
            result = new ConcreteElement();
        }
                
        if (result == null)
        {
            //the customizer already streamed its content, return a stub
            result = new ConcreteElement();
        }

        return result;
    }

    /**
     * Returns a parameter in its defined parameter style
     * 
     * @param data     for this request
     * @param portlet  portlet instance
     * @param parmName parameter name
     * @return current parameter value using specified presentation style
     */
    public static String getPortletParameter(RunData data, Portlet portlet, String parmName)
    {

        if (portlet != null && parmName != null) 
        {
            String parmValue = portlet.getPortletConfig().getInitParameter(parmName, "");
            return getPortletParameter(data, portlet, parmName, parmValue);
        }

        return "";
    }

    /**
     * Returns a parameter in its defined parameter style
     * 
     * @param data      for this request
     * @param portlet   portlet instance
     * @param parmName  parameter name
     * @param parmValue current parameter value
     * @return current parameter value using specified presentation style
     */
    public static String getPortletParameter(RunData data, Portlet portlet, String parmName, String parmValue)
    {
        String result = null;
        try 
        {
            if (portlet != null && parmName != null) 
            {
                // Retrieve registry entry and its parameter
                PortletEntry entry = (PortletEntry) Registry.getEntry(Registry.PORTLET, portlet.getName());
                Parameter param = entry.getParameter(parmName);

                // Verify security for the parameter
                boolean canAccess = JetspeedSecurity.checkPermission((JetspeedUser) data.getUser(), 
                                                                     new PortalResource(entry, param), 
                                                                     JetspeedSecurity.PERMISSION_CUSTOMIZE);
                Map portletParms = portlet.getPortletConfig().getInitParameters();
                String parmStyle = portlet.getPortletConfig().getInitParameter(parmName + ".style");
                if (canAccess)
                {
                    if (parmStyle != null) 
                    {
                        result = ParameterLoader.getInstance().eval(data, 
                                                                    parmStyle, 
                                                                    parmName, 
                                                                    parmValue, 
                                                                    portletParms);                
                    }
                    else
                    {
                        result = "<input type=\"text\" name=\"" + parmName + "\" value=\"" + parmValue + "\"";
                    }
                }
                else
                {
                    // If security does not allow access to specific parameter, allow to provide a fallback parameter
                    String parmNameNoAccess = portlet.getPortletConfig().getInitParameter(parmName + ".style.no-access");
                    if (parmNameNoAccess != null) 
                    {
                        if (Log.getLogger().isDebugEnabled())
                        {
                            Log.debug("JetspeedTool: access to parm [" + parmName + "] disallowed, redirecting to parm [" + 
                                      parmNameNoAccess + "]");
                        }
                        String parmStyleNoAccess = portlet.getPortletConfig().getInitParameter(parmNameNoAccess + ".style");
                        result = ParameterLoader.getInstance().eval(data, 
                                                                    parmStyleNoAccess, 
                                                                    parmNameNoAccess, 
                                                                    parmValue, 
                                                                    portletParms);                
                    }
                }
            }
        } 
        catch (Exception e)
        {
            Log.error(e);
        }

        return result;
    }
    
    /**
     * Retreives the correct SecurityReference for the portlet based on the current
     * profile and the request.
     */
    public SecurityReference getSecurityReference(Entry entry)
    {
        return JetspeedSecurity.getSecurityReference(entry, rundata);
    }
    
    public int getSecuritySource(Entry entry)
    {
        return JetspeedSecurity.getSecuritySource(entry, rundata);
    }
    
    /**
     * Retreives the Entry object for current portlet based on the 
     * "js_peid" parameter
     */
    public Entry getEntryFromRequest() throws Exception
    {
        String jsPeid = rundata.getParameters().getString("js_peid");
        Profile profile = Profiler.getProfile(rundata);
        PSMLDocument doc = profile.getDocument();
        return doc.getEntryById(jsPeid);
    }

}
