/* ====================================================================
 * 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.modules.actions.portlets;

// Jetspeed Stuff
import org.apache.jetspeed.modules.actions.portlets.CustomizeSetAction;
import org.apache.jetspeed.portal.Portlet;
import org.apache.jetspeed.portal.PortletConfig;
import org.apache.jetspeed.portal.PortletException;
import org.apache.jetspeed.portal.PortletSkin;
import org.apache.jetspeed.portal.portlets.VelocityPortlet;
import org.apache.jetspeed.services.persistence.PersistenceManager;
import org.apache.jetspeed.services.Registry;
import org.apache.jetspeed.services.rundata.JetspeedRunData;
import org.apache.jetspeed.om.BaseSecurityReference;
import org.apache.jetspeed.om.profile.Entry;
import org.apache.jetspeed.om.profile.Profile;
import org.apache.jetspeed.om.profile.Skin;
import org.apache.jetspeed.om.profile.psml.PsmlSkin;
import org.apache.jetspeed.om.SecurityReference;
import org.apache.jetspeed.om.registry.PortletEntry;
import org.apache.jetspeed.om.registry.Parameter;
import org.apache.jetspeed.om.security.JetspeedUser;
import org.apache.jetspeed.services.JetspeedSecurity;
import org.apache.jetspeed.services.PortalToolkit;
import org.apache.jetspeed.services.security.PortalResource;
import org.apache.jetspeed.portal.PortletInstance;
import org.apache.jetspeed.util.MetaData;
import org.apache.jetspeed.services.statemanager.SessionState;

// Turbine stuff
import org.apache.turbine.util.Log;
import org.apache.turbine.util.RunData;
import org.apache.turbine.services.template.TurbineTemplate;
import org.apache.turbine.modules.ActionLoader;

// Velocity Stuff
import org.apache.velocity.context.Context;

import java.util.Vector;
import java.util.List;
import java.util.Iterator;

/**
 * This action implements the default portlet behavior customizer
 *
 * <p>Don't call it from the URL, the Portlet and the Action are automatically
 * associated through the registry PortletName
 *
 * @author <a href="mailto:raphael@apache.org">Raphaël Luta</a>
 */
public class CustomizeAction extends VelocityPortletAction
{

    /**
     * Subclasses must override this method to provide default behavior
     * for the portlet action
     *
     * <table>
     * <tr><th>Context      </th><th> Description</th></tr>
     * <!-- ---------  ------------------------- -->
     * <tr><td>action       </td><td> Action to use</td></tr>
     * <tr><td>current_skin </td><td> Current skin for this portlet INSTANCE</td></tr>
     * <tr><td>params       </td><td> List of configurable parameters from the REGISTRY entry.</td></tr>
     * <tr><td>portlet      </td><td> Portlet, not the Portlet Instance!</td></tr>
     * <tr><td>skins        </td><td> List of skins</td></tr>
     * <tr><td>security     </td><td> List of security ref</td></tr>
     * <tr><td>security_ref </td><td> Current securityRef for this portlet INSTANCE</td></tr>
     * </table>
     */
    protected void buildNormalContext( VelocityPortlet portlet,
                                       Context context,
                                       RunData rundata )
    {

        // generic context stuff
        context.put("skins", CustomizeSetAction.buildList(rundata, Registry.SKIN));
        context.put("securitys", CustomizeSetAction.buildList(rundata, Registry.SECURITY));

        // we should first retrieve the portlet to customize
        Portlet p = ((JetspeedRunData)rundata).getCustomized();

        context.put("action", "portlets.CustomizeAction");

        if (p==null) return;

        // retrieve the portlet parameters
        PortletEntry entry = (PortletEntry)Registry.getEntry(Registry.PORTLET,p.getName());
        // save the entry in the session
        Vector params = new Vector();
        Iterator i = entry.getParameterNames();

        while(i.hasNext())
        {
            String name = (String)i.next();
            Parameter param = entry.getParameter(name);

            // filter some "system" and hidden parameters
            if (  (!param.isHidden()) && (name.charAt(0)!='_') )
            {
                // check the user role
                if (JetspeedSecurity.checkPermission((JetspeedUser)rundata.getUser(), new PortalResource( entry, param), JetspeedSecurity.PERMISSION_CUSTOMIZE))
                {
                    params.add(entry.getParameter(name));
                }
            }
        }

        // get the customization state for this page
        SessionState customizationState = ((JetspeedRunData)rundata).getPageSessionState();
        customizationState.setAttribute("customize-parameters", params);

        // populate the customizer context
        context.put("parameters", params);
        context.put("portlet", p);
        context.put("customizer", portlet);

        if (p.getPortletConfig().getSecurityRef() != null)
          context.put("security_ref", p.getPortletConfig().getSecurityRef().getParent());
        if (p.getPortletConfig().getSkin() != null)
            context.put("current_skin", p.getPortletConfig().getPortletSkin().getName());

        Profile profile = ((JetspeedRunData)rundata).getCustomizedProfile();
        String currentTitle = profile.getDocument().getEntryById(p.getID()).getTitle();
        if (currentTitle == null && p.getPortletConfig().getMetainfo() != null)
        {
            currentTitle = p.getPortletConfig().getMetainfo().getTitle();
        }
        context.put("current_title", currentTitle);

    }

    /** Clean up the customization state */
    public void doCancel(RunData rundata, Context context)
    {
        ((JetspeedRunData)rundata).setCustomized(null);
        if (((JetspeedRunData)rundata).getCustomized()==null)
        {
            try
            {
                ActionLoader.getInstance().exec( rundata, "controls.EndCustomize" );
            }
            catch (Exception e)
            {
                Log.error("Unable to load action controls.EndCustomize ",e);
            }
        }
    }

    /** Updates the customized portlet entry */
    public void doUpdate(RunData rundata, Context context)
    {
        // get the customization state for this page
        SessionState customizationState = ((JetspeedRunData)rundata).getPageSessionState();

        // we should first retrieve the portlet to customize and its parameters
        // definition
        Portlet p = ((JetspeedRunData)rundata).getCustomized();
        Vector params = (Vector) customizationState.getAttribute("customize-parameters");
        String newSecurityParent = rundata.getParameters().getString("_security_ref");
        String newSkinName = (String) rundata.getParameters().getString("_skin");
        String newTitle = (String) rundata.getParameters().getString("current_title");

        boolean changeRequested = ( (params == null) || (newSkinName == null) || (newSecurityParent == null) || (newTitle == null));
        boolean madePsChange = false;
        boolean madePcChange = false;

        if ((p==null) || (changeRequested = false ))
        {
            doCancel(rundata, context);
            return;
        }
        PortletConfig pc = p.getPortletConfig();
        Profile profile = ((JetspeedRunData)rundata).getCustomizedProfile();
        Entry entry = profile.getDocument().getEntryById(p.getID());

        // Only update the security ref if the parent changed
        if ((newSecurityParent != null))
        {
            boolean securityChanged = false;
            SecurityReference currentSecurityRef = pc.getSecurityRef();
            if (currentSecurityRef != null)
            {
                securityChanged = (newSecurityParent.equals(currentSecurityRef.getParent()) == false);
            }
            else
            {
                securityChanged = (newSecurityParent.trim().length() > 0);
            }
            if (securityChanged == true)
            {
                SecurityReference securityRef = null;
                if ((newSecurityParent.trim().length() > 0))
                {
                    securityRef = new BaseSecurityReference();
                    securityRef.setParent( newSecurityParent);
                }
                // Note: setting the portlet's config may not be a good idea -
                // it might be used as the Portlet for other PSMLDocument Entries that
                // have a different idea of security - and the caching of Portlets does
                // NOT include security -ggolden.
                pc.setSecurityRef(securityRef);
                entry.setSecurityRef(securityRef);
                madePcChange = true;
            }
        }

        // Only update the skin if the name changed
        if (newSkinName != null)
        {
            boolean skinChanged = false;
            String currentSkinName = null;

            if  (pc.getSkin() != null)
                currentSkinName = pc.getPortletSkin().getName();

            if (currentSkinName != null)
            {
                skinChanged = (newSkinName.equals(currentSkinName) == false);
            }
            else
            {
                skinChanged = (newSkinName.trim().length() > 0);
            }

            if (skinChanged == true)
            {
                PortletSkin skin = null;
                if ((newSkinName.trim().length() > 0))
                {
                    skin = PortalToolkit.getSkin(newSkinName);
                    if (skin != null)
                    {
                        // Note: setting the portlet's config may not be a good idea -
                        // it might be used as the Portlet for other PSMLDocument Entries that
                        // have a different idea of skin - and the caching of Portlets does
                        // NOT include skin -ggolden.
                        pc.setPortletSkin(skin);

                        Skin psmlSkin = entry.getSkin();
                        if (psmlSkin == null)
                        {
                            entry.setSkin(new PsmlSkin());
                        }
                        entry.getSkin().setName(newSkinName);
                    }
                    else
                    {
                        Log.warn( "Unable to update skin for portlet entry " + entry.getId() + " because skin " + skin + " does not exist.");
                    }
                }
                else
                {
                    // Note: setting the portlet's config may not be a good idea -
                    // it might be used as the Portlet for other PSMLDocument Entries that
                    // have a different idea of skin - and the caching of Portlets does
                    // NOT include skin -ggolden.
                    pc.setPortletSkin( null);
                    entry.setSkin(null);
                }
                madePcChange = true;
            }
        }

        // Only update the title if the title changed
        if (newTitle != null)
        {
            boolean titleChanged = false;
            String currentTitle = entry.getTitle();

            MetaData md = pc.getMetainfo();
            if  (currentTitle == null && md != null && md.getTitle() != null)
                currentTitle = md.getTitle();

            if (currentTitle != null)
            {
                titleChanged = (newTitle.equals(currentTitle) == false);
            }
            else
            {
                titleChanged = (newTitle.trim().length() > 0);
            }

            if (titleChanged == true)
            {

                if ((newTitle.trim().length() > 0))
                {
                    // Note: setting the portlet's config may not be a good idea -
                    // it might be used as the Portlet for other PSMLDocument Entries that
                    // have a different idea of title - and the caching of Portlets does
                    // NOT include title -ggolden.
                    if (md == null) {
                        md = new MetaData();
                        pc.setMetainfo(md);
                    }
                    md.setTitle(newTitle);
                    entry.setTitle(newTitle);
                    madePcChange = true;
                }
            }
        }

        // Update paramaters
        try
        {
            PortletInstance instance = PersistenceManager.getInstance(p, rundata);

            Iterator i = params.iterator();

            while(i.hasNext())
            {
                Parameter param = (Parameter)i.next();
                String name = param.getName();
                String newValue = rundata.getParameters().getString(name);

                // we treat null and empty string in the same way, we should
                // probably find a better system as they may have different semantic
                if ((newValue==null)||(newValue.equals("")))
                {
                    // this will fail if the parameter is currently set in the registry
                    // but not in the PSML entry. This is a good thing !
                    instance.removeAttribute(name);
                }
                else
                {
                    // only update if the value changed
                    if (!newValue.equals(p.getPortletConfig().getInitParameter(name)))
                    {
                        instance.setAttribute(name,newValue);
                        // Note: setting the portlet's config *IS NOT* a good idea -
                        // it might be used as the Portlet for other PSMLDocument Entries that
                        // used to have the same parameters as this one (Portlets *ARE* cached on
                        // their parameters), so changing the parameters like this changes them for
                        // all and make the Portlet *NOT MATCH* the cache handle.
                        // It's ok not to do this here, since when this PSML is next rendered,
                        // it will look for a portlet with the new set of parameters, and will
                        // find / create a new one if needed - ggolden

                        //pc.setInitParameter(name,newValue);
                        madePsChange = true;
                    }
                }
            }

            // save all the changes
            if ((madePsChange == true) || (madePcChange == true))
            {
                try
                {
                    ((JetspeedRunData) rundata).getCustomizedProfile().store();
                }
                catch (Exception e)
                {
                    Log.error("Unable to save profile ",e);
                }
            }
            //FIXME: this hack is due to the corrupted lifecycle of the portlet in the
            //current API when caching is activated
            try
            {
                org.apache.jetspeed.util.PortletSessionState.setPortletConfigChanged(p, rundata);
                p.init();
            }
            catch (PortletException e)
            {
                Log.error("Customizer failed to reinitialize the portlet "+p.getName(), e);
            }

            // we're done, make sure clean up the
            // session
            doCancel(rundata, context);
        }
        catch (Exception e)
        {
            Log.error(e);
        }
    }
}
