/*
 * JBoss, a division of Red Hat
 * Copyright 2010, Red Hat Middleware, LLC, and individual
 * contributors as indicated by the @authors tag. See the
 * copyright.txt in the distribution for a full listing of
 * individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.exoplatform.portal.webui.application;

import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.portlet.PortletMode;
import javax.portlet.ResourceResponse;
import javax.portlet.WindowState;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.namespace.QName;

import org.exoplatform.commons.utils.Safe;
import org.exoplatform.portal.Constants;
import org.exoplatform.portal.application.PortalRequestContext;
import org.exoplatform.portal.webui.page.UIPage;
import org.exoplatform.portal.webui.page.UIPageBody;
import org.exoplatform.portal.webui.portal.UIPortal;
import org.exoplatform.portal.webui.util.Util;
import org.exoplatform.portal.webui.workspace.UIMaskWorkspace;
import org.exoplatform.portal.webui.workspace.UIPortalApplication;
import org.exoplatform.portal.webui.workspace.UIWorkingWorkspace;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.web.application.ApplicationMessage;
import org.exoplatform.webui.application.WebuiRequestContext;
import org.exoplatform.webui.core.UIComponent;
import org.exoplatform.webui.event.Event;
import org.exoplatform.webui.event.Event.Phase;
import org.exoplatform.webui.event.EventListener;
import org.gatein.common.util.MultiValuedPropertyMap;
import org.gatein.common.util.ParameterValidation;
import org.gatein.pc.api.Mode;
import org.gatein.pc.api.ParametersStateString;
import org.gatein.pc.api.PortletContext;
import org.gatein.pc.api.StateString;
import org.gatein.pc.api.StatefulPortletContext;
import org.gatein.pc.api.invocation.ActionInvocation;
import org.gatein.pc.api.invocation.EventInvocation;
import org.gatein.pc.api.invocation.ResourceInvocation;
import org.gatein.pc.api.invocation.response.ContentResponse;
import org.gatein.pc.api.invocation.response.ErrorResponse;
import org.gatein.pc.api.invocation.response.HTTPRedirectionResponse;
import org.gatein.pc.api.invocation.response.PortletInvocationResponse;
import org.gatein.pc.api.invocation.response.ResponseProperties;
import org.gatein.pc.api.invocation.response.SecurityErrorResponse;
import org.gatein.pc.api.invocation.response.SecurityResponse;
import org.gatein.pc.api.invocation.response.UpdateNavigationalStateResponse;

/** May 29, 2006 */
public class UIPortletActionListener {

    public static final String PORTLET_EVENTS = "PortletEvents";

    public static final String CHANGE_WINDOW_STATE_EVENT = "PortletChangeWindowStateEvent";
    public static final String CHANGE_PORTLET_MODE_EVENT = "ChangePortletModeEvent";

    protected static Log log = ExoLogger.getLogger("portal:UIPortletActionListener");

    /**
     * The process action listener is called when an ActionURL generated by the portlet container has been invoked by the client
     * <p/>
     * The call is delegated to the portlet container iteself using the method portletContainer.processAction(...). It returns
     * an object of type ActionOutput that contains several information such as the next window state and portlet modes (if they
     * have to change) as well as a list of Events to be broadcasted to the other portlets located in the same portal page
     */
    public static class ProcessActionActionListener<S, C extends Serializable, I> extends EventListener<UIPortlet<S, C>> {
        public void execute(Event<UIPortlet<S, C>> event) throws Exception {
            UIPortlet<S, C> uiPortlet = event.getSource();
            PortalRequestContext prcontext = (PortalRequestContext) event.getRequestContext();

            // set the public render parameters from the request before creating the invocation
            HttpServletRequest request = prcontext.getRequest();
            setupPublicRenderParams(uiPortlet, request.getParameterMap());

            // set the navigational state
            String navState = prcontext.getRequestParameter(ExoPortletInvocationContext.NAVIGATIONAL_STATE_PARAM_NAME);
            if (navState != null) {
                uiPortlet.setNavigationalState(ParametersStateString.create(navState));
            }

            //
            ActionInvocation actionInvocation = uiPortlet.create(ActionInvocation.class, prcontext);
            if (actionInvocation == null) {
                return;
            }
            //
            PortletInvocationResponse portletResponse = uiPortlet.invoke(actionInvocation);

            // deal with potential portlet context modifications
            ExoPortletInstanceContext instanceCtx = (ExoPortletInstanceContext) actionInvocation.getInstanceContext();
            if (instanceCtx.getModifiedContext() != null) {
                StatefulPortletContext<C> updatedCtx = (StatefulPortletContext<C>) instanceCtx.getModifiedContext();
                C portletState = uiPortlet.getModifiedState(updatedCtx);
                uiPortlet.update(portletState);
            } else {
                // todo: fix me as this shouldn't probably be done only for the WSRP case
                PortletContext clonedContext = instanceCtx.getClonedContext();
                if (clonedContext != null) {
                    C state = uiPortlet.getClonedState(clonedContext);
                    uiPortlet.update(state);
                }
            }

            if (portletResponse instanceof UpdateNavigationalStateResponse) {
                handleUpdateNavigationalStateResponse((UpdateNavigationalStateResponse) portletResponse, uiPortlet, prcontext);
            } else if (portletResponse instanceof HTTPRedirectionResponse) {
                handleRedirectionResponse((HTTPRedirectionResponse) portletResponse, prcontext.getResponse());
                prcontext.setResponseComplete(true);
            } else if (portletResponse instanceof ErrorResponse) {
                handleErrorResponse((ErrorResponse) portletResponse);
            } else if (portletResponse instanceof SecurityResponse) {
                handleSecurityResponse((SecurityResponse) portletResponse);
            } else {
                throw new Exception("Unexpected response type [" + portletResponse + "]. Expected an UpdateNavigationResponse"
                        + ", a HTTPRedirectionResponse or an ErrorResponse.");
            }
        }

        private void handleRedirectionResponse(HTTPRedirectionResponse redirectionResponse, HttpServletResponse response)
                throws IOException {
            String redirectionURL = redirectionResponse.getLocation();
            response.sendRedirect(redirectionURL);
        }

        private void handleUpdateNavigationalStateResponse(UpdateNavigationalStateResponse navStateResponse,
                UIPortlet<S, C> uiPortlet, PortalRequestContext prcontext) throws Exception {
            /*
             * Update the portlet window state according to the action output information
             *
             * If the current node is displaying a usual layout page, also tells the page which portlet to render or not when
             * the state is maximized
             */
            // Note: we should only update the WindowState if the UpdateNavigationalStateResponse.getWindowState is not null,
            // otherwise it means the WindowState has not changed and we should use the current value.
            if (navStateResponse.getWindowState() != null) {
                WindowState state = new WindowState(getWindowStateOrDefault(navStateResponse));
                setNextState(uiPortlet, state);
            }

            // update the portlet with the next mode to display
            // Note: we should only update the Mode if the UpdateNavigationalStateResponse.getMode is not null,
            // otherwise it means the mode has not changed and we should use the current value.
            if (navStateResponse.getMode() != null) {
                PortletMode mode = new PortletMode(getPortletModeOrDefault(navStateResponse));
                setNextMode(uiPortlet, mode);
            }

            /*
             * Cache the render parameters in the UI portlet component to handle the navigational state. Each time a portlet is
             * rendered (except using directly a RenderURL) those parameters are added to the portlet request to preserve the
             * portlet state among all the portal clicks
             */

            //
            StateString navigationalState = navStateResponse.getNavigationalState();
            if (navigationalState != null) {
                uiPortlet.setNavigationalState(navigationalState);
            }

            // update the public render parameters with the changes from the invocation
            setupPublicRenderParams(uiPortlet, navStateResponse.getPublicNavigationalStateUpdates());

            /*
             * Handle the events returned by the action output and broadcast a new UI event to the ProcessEventsActionListener
             * that will then target the portlet container service directly
             */

            // TODO: (mwringe) add this to the UpdateNavigationStateResponse.Event class instead of here
            class PortletEvent implements javax.portlet.Event {
                QName qName;

                Serializable value;

                public PortletEvent(QName qName, Serializable value) {
                    this.qName = qName;
                    this.value = value;
                }

                public String getName() {
                    return qName.getLocalPart();
                }

                public QName getQName() {
                    return qName;
                }

                public Serializable getValue() {
                    return value;
                }
            }

            List<UpdateNavigationalStateResponse.Event> nsEvents = navStateResponse.getEvents();
            List<javax.portlet.Event> events = new ArrayList<javax.portlet.Event>(nsEvents.size());
            if (nsEvents != null && !nsEvents.isEmpty()) {
                for (UpdateNavigationalStateResponse.Event nsEvent : nsEvents) {
                    if (uiPortlet.supportsPublishingEvent(nsEvent.getName())) {
                        javax.portlet.Event portletEvent = new PortletEvent(nsEvent.getName(), nsEvent.getPayload());
                        events.add(portletEvent);
                    }
                }
            }

            if (events != null) {
                prcontext.setAttribute(PORTLET_EVENTS, new EventsWrapper(events));
                uiPortlet.createEvent("ProcessEvents", Phase.PROCESS, prcontext).broadcast();
            }

        }

        private void handleErrorResponse(ErrorResponse response) throws Exception {
            throw (Exception) response.getCause();
        }

        private void handleSecurityResponse(SecurityResponse response) throws Exception {
            if (response instanceof SecurityErrorResponse) {
                SecurityErrorResponse securityErrorResponse = (SecurityErrorResponse) response;
                throw new Exception("SecurityErrorResponse Returned while trying to process portlet action. ",
                        securityErrorResponse.getThrowable());
            } else {
                throw new Exception("Security Response of type " + response.getClass()
                        + " encountered while trying to process portlet action.");
            }
        }
    }

    /**
     * This method is used to set the next portlet window state if this one needs to be modified because of the incoming request
     */
    public static void setNextState(UIPortlet uiPortlet, WindowState state) {
        if (state != null) {
            UIPage uiPage = uiPortlet.getAncestorOfType(UIPage.class);
            if (WindowState.MAXIMIZED.equals(state)) {
                uiPortlet.setCurrentWindowState(WindowState.MAXIMIZED);
                if (uiPage != null) {
                    uiPage.setMaximizedUIPortlet(uiPortlet);
                }
            } else if (WindowState.MINIMIZED.equals(state)) {
                uiPortlet.setCurrentWindowState(WindowState.MINIMIZED);
                if (uiPage != null) {
                    uiPage.setMaximizedUIPortlet(null);
                }
            } else {
                uiPortlet.setCurrentWindowState(WindowState.NORMAL);
                if (uiPage != null) {
                    uiPage.setMaximizedUIPortlet(null);
                }
            }
        }
    }

    /** This method is used to set the next portlet mode if this one needs to be modified because of the incoming request */
    public static void setNextMode(UIPortlet uiPortlet, PortletMode portletMode) {
        if (portletMode != null) {
            if (portletMode.equals(PortletMode.HELP)) {
                uiPortlet.setCurrentPortletMode(PortletMode.HELP);
            } else if (portletMode.equals(PortletMode.EDIT)) {
                uiPortlet.setCurrentPortletMode(PortletMode.EDIT);
            } else {
                uiPortlet.setCurrentPortletMode(PortletMode.VIEW);
            }
        }
    }

    /**
     * The serveResource() method defined in the JSR 286 specs has several goals: - provide binary output like images to be
     * displayed in the portlet (in the previous spec - JSR 168 - a servlet was needed) - provide text output that does not
     * impact the entire portal rendering, it is for instance usefull when dealing with Javascript to return some JSON
     * structures
     * <p/>
     * The method delegates the call to the portlet container serverResource method after filling the ResourceInput object with
     * the current request state.
     * <p/>
     * This returns a ResourceOutput object that can content binary or text contentType
     * <p/>
     * Finally the content is set in the portal response writer or outputstream depending on the type; the processRender()
     * method of the portal is not called as we set the response as complete
     */
    public static class ServeResourceActionListener<S, C extends Serializable, I> extends EventListener<UIPortlet<S, C>> {
        public void execute(Event<UIPortlet<S, C>> event) throws Exception {
            UIPortlet<S, C> uiPortlet = event.getSource();
            log.trace("Serve Resource for portlet: " + uiPortlet.getPortletContext());
            String resourceId = null;

            //
            PortalRequestContext context = (PortalRequestContext) event.getRequestContext();
            HttpServletResponse response = context.getResponse();

            //
            try {
                // Set the NavigationalState
                String navState = context.getRequestParameter(ExoPortletInvocationContext.NAVIGATIONAL_STATE_PARAM_NAME);
                if (navState != null) {
                    uiPortlet.setNavigationalState(ParametersStateString.create(navState));
                }

                //
                ResourceInvocation resourceInvocation = uiPortlet.create(ResourceInvocation.class, context);

                // set the resourceId to be used in case of a problem
                resourceId = resourceInvocation.getResourceId();

                //
                PortletInvocationResponse portletResponse = uiPortlet.invoke(resourceInvocation);

                //
                int statusCode;
                MultiValuedPropertyMap<String> transportHeaders;
                String contentType;
                String charset;
                Object content;
                if (!(portletResponse instanceof ContentResponse)) {
                    if (portletResponse instanceof ErrorResponse) {
                        ErrorResponse errorResponse = (ErrorResponse) portletResponse;
                        Throwable cause = errorResponse.getCause();
                        if (cause != null) {
                            log.trace("Got error response from portlet", cause);
                        } else if (errorResponse.getMessage() != null) {
                            log.trace("Got error response from portlet:" + errorResponse.getMessage());
                        } else {
                            log.trace("Got error response from portlet");
                        }
                    } else {
                        log.trace("Unexpected response type [" + portletResponse
                                + "]. Expected a ContentResponse or an ErrorResponse.");
                    }
                    statusCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
                    contentType = null;
                    charset = null;
                    transportHeaders = null;
                    content = null;
                } else {
                    //
                    ContentResponse piResponse = (ContentResponse) portletResponse;
                    ResponseProperties properties = piResponse.getProperties();
                    transportHeaders = properties != null ? properties.getTransportHeaders() : null;

                    // Look at status code if there is one and honour it
                    String status = transportHeaders != null ? transportHeaders.getValue(ResourceResponse.HTTP_STATUS_CODE)
                            : null;
                    if (status != null) {
                        try {
                            statusCode = Integer.parseInt(status);
                        } catch (NumberFormatException e) {
                            statusCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
                        }
                    } else {
                        statusCode = HttpServletResponse.SC_OK;
                    }

                    //
                    contentType = piResponse.getContentType();
                    charset = piResponse.getEncoding();

                    //
                    log.trace("Try to get a resource of type: " + contentType + " for the portlet: "
                            + uiPortlet.getPortletContext());
                    if (piResponse.getChars() != null) {
                        content = piResponse.getChars();
                    } else if (piResponse.getBytes() != null) {
                        content = piResponse.getBytes();
                    } else {
                        content = null;
                    }
                }

                //
                response.setStatus(statusCode);

                // Set content type if any
                if (contentType != null) {
                    response.setContentType(contentType);
                }

                // Set encoding
                if (charset != null) {
                    response.setCharacterEncoding(charset);
                }

                // Send headers if any
                if (transportHeaders != null) {
                    sendHeaders(transportHeaders, context);
                }

                // Send body if any
                if (content instanceof String) {
                    context.getWriter().write((String) content);
                } else if (content instanceof byte[]) {
                    byte[] bytes = (byte[]) content;
                    response.setContentLength(bytes.length);
                    OutputStream stream = response.getOutputStream();
                    try {
                        stream.write(bytes);
                    } finally {
                        Safe.close(stream);
                    }
                }

                //
                response.flushBuffer();
            } catch (Exception e) {
                if (!e.getClass().toString().contains("ClientAbortException")) {
                    log.error("Problem while serving resource " + (resourceId != null ? resourceId : "") + " for the portlet: "
                            + uiPortlet.getPortletContext().getId(), e);
                }
            } finally {
                /**
                 * The resource method does not need to go through the render phase
                 */
                event.getRequestContext().setResponseComplete(true);
            }
        }

        /**
         * Send any header to the client
         *
         * @param headers the headers
         * @param context the context
         * @throws IOException any io exception
         */
        private void sendHeaders(MultiValuedPropertyMap<String> headers, PortalRequestContext context) {
            Map<String, String> map = new HashMap<String, String>();
            for (String key : headers.keySet()) {
                for (String value : headers.getValues(key)) {
                    map.put(key, value);
                }
            }

            // We need to remove it if it there
            map.remove(ResourceResponse.HTTP_STATUS_CODE);
            context.setHeaders(map);
        }
    }

    /**
     * Process Events sent by the portlet API during the processAction() and serverResource() methods defined in Portlet API 2.0
     * (JSR 286)
     */
    public static class ProcessEventsActionListener extends EventListener<UIPortlet> {
        public void execute(Event<UIPortlet> event) throws Exception {
            UIPortlet uiPortlet = event.getSource();
            PortalRequestContext context = (PortalRequestContext) event.getRequestContext();
            List<UIPortlet> portletInstancesInPage = new ArrayList<UIPortlet>();
            // UIPortalApplication uiPortal = uiPortlet.getAncestorOfType(UIPortalApplication.class);
            UIPortalApplication uiPortal = (UIPortalApplication) context.getUIApplication();
            uiPortal.findComponentOfType(portletInstancesInPage, UIPortlet.class);
            EventsWrapper eventsWrapper = (EventsWrapper) event.getRequestContext().getAttribute(PORTLET_EVENTS);
            List<javax.portlet.Event> events = eventsWrapper.getEvents();

            /*
             * Iterate over all the events that the processAction has generated. Check among all the portlet instances deployed
             * in the page (usual layout or webos) which instance can be targeted by the event and then process the event on the
             * associated UIPortlet component
             */
            while (events.size() > 0) {
                javax.portlet.Event nativeEvent = events.remove(0);
                QName eventName = nativeEvent.getQName();
                for (Iterator<UIPortlet> iterator = portletInstancesInPage.iterator(); iterator.hasNext();) {
                    UIPortlet uiPortletInPage = iterator.next();
                    if (uiPortletInPage.supportsProcessingEvent(eventName)
                            && !eventsWrapper.isInvokedTooManyTimes(uiPortletInPage)) {
                        List<javax.portlet.Event> newEvents = processEvent(uiPortletInPage, nativeEvent);
                        eventsWrapper.increaseCounter(uiPortletInPage);
                        if (context.useAjax()) {
                            log.info("Events were generated inside the scope of an AJAX call, hence will only refresh the targeted portlets");
                            event.getRequestContext().addUIComponentToUpdateByAjax(uiPortletInPage);
                        } else {
                            log.info("Events were generated outside the scope of an AJAX call, hence will make a full render of the page");
                            context.ignoreAJAXUpdateOnPortlets(true);
                        }
                        if (newEvents != null && !newEvents.isEmpty()) {
                            log.trace("The portlet: " + uiPortletInPage.getPortletContext().getId()
                                    + " processEvent() method has generated new events itself");
                            events.addAll(newEvents);
                        }
                    }
                }
            }
        }
    }

    /**
     * This method is called when the javax.portlet.Event is supported by the current portlet stored in the Portlet Caontainer
     * <p/>
     * The processEvent() method can also generates IPC events and hence the portal itself will call the
     * ProcessEventsActionListener once again
     */
    public static <S, C extends Serializable, I> List<javax.portlet.Event> processEvent(UIPortlet<S, C> uiPortlet,
            javax.portlet.Event event) {
        log.trace("Process Event: " + event.getName() + " for portlet: " + uiPortlet.getState());
        try {
            PortalRequestContext context = (PortalRequestContext) WebuiRequestContext.getCurrentInstance();

            //
            EventInvocation eventInvocation = uiPortlet.create(EventInvocation.class, context);

            //
            eventInvocation.setName(event.getQName());
            eventInvocation.setPayload(event.getValue());

            //
            PortletInvocationResponse piResponse = uiPortlet.invoke(eventInvocation);

            //
            ExoPortletInstanceContext instanceCtx = (ExoPortletInstanceContext) eventInvocation.getInstanceContext();
            if (instanceCtx.getModifiedContext() != null) {
                StatefulPortletContext<C> updatedCtx = (StatefulPortletContext<C>) instanceCtx.getModifiedContext();
                C portletState = updatedCtx.getState();
                uiPortlet.update(portletState);
            }

            // todo: handle the error response better than this.
            if (!(piResponse instanceof UpdateNavigationalStateResponse)) {
                if (piResponse instanceof ErrorResponse) {
                    ErrorResponse errorResponse = (ErrorResponse) piResponse;
                    throw (Exception) errorResponse.getCause();
                } else {
                    throw new Exception("Unexpected response type [" + piResponse
                            + "]. Expected a UpdateNavigationResponse or an ErrorResponse.");
                }
            }

            UpdateNavigationalStateResponse navResponse = (UpdateNavigationalStateResponse) piResponse;

            //

            /*
             * Update the portlet window state according to the action output information
             *
             * If the current node is displaying a usual layout page, also tells the page which portlet to render or not when
             * the state is maximized
             */
            WindowState state = new WindowState(getWindowStateOrDefault(navResponse));
            setNextState(uiPortlet, state);

            // update the portlet with the next mode to display
            PortletMode mode = new PortletMode(getPortletModeOrDefault(navResponse));
            setNextMode(uiPortlet, mode);

            StateString navState = navResponse.getNavigationalState();
            if (navState != null) {
                uiPortlet.setNavigationalState(navResponse.getNavigationalState());
            }
            setupPublicRenderParams(uiPortlet, navResponse.getPublicNavigationalStateUpdates());

            // TODO: (mwringe) add this to the UpdateNavigationStateResponse.Event class instead of here
            class PortletEvent implements javax.portlet.Event {
                QName qName;

                Serializable value;

                public PortletEvent(QName qName, Serializable value) {
                    this.qName = qName;
                    this.value = value;
                }

                public String getName() {
                    return qName.getLocalPart();
                }

                public QName getQName() {
                    return qName;
                }

                public Serializable getValue() {
                    return value;
                }
            }

            List<UpdateNavigationalStateResponse.Event> nsEvents = navResponse.getEvents();
            List<javax.portlet.Event> events = new ArrayList<javax.portlet.Event>(nsEvents.size());
            if (nsEvents != null && !nsEvents.isEmpty()) {
                for (UpdateNavigationalStateResponse.Event nsEvent : nsEvents) {
                    javax.portlet.Event portletEvent = new PortletEvent(nsEvent.getName(), nsEvent.getPayload());
                    events.add(portletEvent);
                }
            }

            return events;
        } catch (Exception e) {
            log.error("Problem while processesing event for the portlet: " + uiPortlet.getState(), e);
        }
        return null;
    }

    private static String getPortletModeOrDefault(UpdateNavigationalStateResponse navResponse) {
        Mode mode = navResponse.getMode();
        if (mode == null) {
            mode = Mode.VIEW;
        }
        return mode.toString();
    }

    private static String getWindowStateOrDefault(UpdateNavigationalStateResponse navResponse) {
        org.gatein.pc.api.WindowState state = navResponse.getWindowState();
        if (state == null) {
            state = org.gatein.pc.api.WindowState.NORMAL;
        }
        return state.toString();
    }

    /**
     * This listener is called when a RenderURL url has been generated by the portlet container. In that case it means that the
     * render() method of a targeted portlet will be directly called and that the existing navigational state will be reset by
     * removing all the Render Parameters from the cache map located in the UIPortlet
     */
    public static class RenderActionListener extends EventListener<UIPortlet> {
        public void execute(Event<UIPortlet> event) throws Exception {
            UIPortlet uiPortlet = event.getSource();
            uiPortlet.setNavigationalState(null);

            // set the public params
            HttpServletRequest request = event.getRequestContext().getRequest();
            setupPublicRenderParams(uiPortlet, request.getParameterMap());

            // set render params
            String navState = event.getRequestContext().getRequestParameter(
                    ExoPortletInvocationContext.NAVIGATIONAL_STATE_PARAM_NAME);
            if (navState != null) {
                uiPortlet.setNavigationalState(ParametersStateString.create(navState));
            }
        }
    }

    /**
     * This method is called by the process action and render action listeners, aka during the processDecode() phase of our UI
     * framework
     * <p/>
     * It goes throughs all the request parameters and add to the public render parameters Map the one that are supported by the
     * targeted portlet
     */
    public static void setupPublicRenderParams(UIPortlet uiPortlet, Map<String, String[]> requestParams) {
        if (ParameterValidation.existsAndIsNotEmpty(requestParams)) {
            UIPortal uiPortal = Util.getUIPortal();
            Map<String, String[]> publicParams = uiPortal.getPublicParameters();

            for (String key : requestParams.keySet()) {
                String[] value = requestParams.get(key);
                if (uiPortlet.supportsPublicParam(key)) {
                    if (value.length > 0) {
                        publicParams.put(key, value);
                    } else {
                        publicParams.remove(key);
                    }
                }
            }
        }

    }

    public static class ChangeWindowStateActionListener extends EventListener<UIPortlet> {
        public void execute(Event<UIPortlet> event) throws Exception {
            UIPortlet uiPortlet = event.getSource();

            UIPortalApplication uiPortalApp = uiPortlet.getAncestorOfType(UIPortalApplication.class);
            UIWorkingWorkspace uiWorkingWS = uiPortalApp.getChildById(UIPortalApplication.UI_WORKING_WS_ID);
            PortalRequestContext pcontext = (PortalRequestContext) event.getRequestContext();
            pcontext.addUIComponentToUpdateByAjax(uiWorkingWS);
            pcontext.ignoreAJAXUpdateOnPortlets(true);

            String windowState = null;

            Object changeWindowStateAttribute = event.getRequestContext().getAttribute(CHANGE_WINDOW_STATE_EVENT);
            if (changeWindowStateAttribute != null && changeWindowStateAttribute instanceof String) {
                windowState = (String) changeWindowStateAttribute;
            }

            if (windowState == null) {
                windowState = event.getRequestContext().getRequestParameter(Constants.PORTAL_WINDOW_STATE);
            }
            if (windowState == null) {
                windowState = event.getRequestContext().getRequestParameter(UIComponent.OBJECTID).trim();
            }

            if (windowState == null) {
                windowState = uiPortlet.getCurrentWindowState().toString();
            }

            UIPageBody uiPageBody = uiPortlet.getAncestorOfType(UIPageBody.class);
            UIPage uiPage = uiPortlet.getAncestorOfType(UIPage.class);
            if (windowState.equals(WindowState.MAXIMIZED.toString())) {
                if (uiPageBody != null) {
                    uiPortlet.setCurrentWindowState(WindowState.MAXIMIZED);
                    // TODO dang.tung: we have to set maximized portlet for page because in ShowMaxWindow case the PageBody
                    // isn't rendered
                    // reference: UIPortalLifecycle, UIPageLifecycle, renderChildren() in UIPageBody
                    // ---------------------------------------------------------
                    if (uiPage != null && uiPage.isShowMaxWindow()) {
                        uiPage.setMaximizedUIPortlet(uiPortlet);
                    }
                    // ---------------------------------------------------------
                    uiPageBody.setMaximizedUIComponent(uiPortlet);
                } else {
                    uiPortlet.setCurrentWindowState(WindowState.NORMAL);
                }
                return;
            }
            if (uiPageBody != null) {
                UIPortlet maxPortlet = (UIPortlet) uiPageBody.getMaximizedUIComponent();
                if (maxPortlet == uiPortlet) {
                    uiPageBody.setMaximizedUIComponent(null);
                }
            }
            // TODO dang.tung: for ShowMaxWindow situation
            // ----------------------------------------------------------------
            if (uiPage != null) {
                UIPortlet maxPortlet = (UIPortlet) uiPage.getMaximizedUIPortlet();
                if (maxPortlet == uiPortlet) {
                    uiPage.setMaximizedUIPortlet(null);
                }
            }
            // -----------------------------------------------------------------
            if (windowState.equals(WindowState.MINIMIZED.toString())) {
                uiPortlet.setCurrentWindowState(WindowState.MINIMIZED);
                return;
            }
            uiPortlet.setCurrentWindowState(WindowState.NORMAL);

        }
    }

    /** This listener is called when the portlet mode of a portlet has to be changed. */
    public static class ChangePortletModeActionListener extends EventListener<UIPortlet> {
        public void execute(Event<UIPortlet> event) throws Exception {
            UIPortlet uiPortlet = event.getSource();

            String portletMode = null;

            Object changePortletModeAttribute = event.getRequestContext().getAttribute(CHANGE_PORTLET_MODE_EVENT);
            if (changePortletModeAttribute != null && changePortletModeAttribute instanceof String) {
                portletMode = (String) changePortletModeAttribute;
            }

            if (portletMode == null) {
                portletMode = event.getRequestContext().getRequestParameter(Constants.PORTAL_PORTLET_MODE);
            }
            if (portletMode == null) {
                portletMode = event.getRequestContext().getRequestParameter(UIComponent.OBJECTID);
            }
            if (portletMode == null) {
                portletMode = uiPortlet.getCurrentPortletMode().toString();
            }

            log.trace("Change portlet mode of " + uiPortlet.getPortletContext().getId() + " to " + portletMode);
            if (portletMode.equals(PortletMode.HELP.toString())) {
                uiPortlet.setCurrentPortletMode(PortletMode.HELP);
            } else if (portletMode.equals(PortletMode.EDIT.toString())) {
                uiPortlet.setCurrentPortletMode(PortletMode.EDIT);
            } else if (portletMode.equals(PortletMode.VIEW.toString())) {
                uiPortlet.setCurrentPortletMode(PortletMode.VIEW);
            } else {
                PortletMode customMode = new PortletMode(portletMode);
                uiPortlet.setCurrentPortletMode(customMode);
            }
            event.getRequestContext().addUIComponentToUpdateByAjax(uiPortlet);
        }
    }

    /**
     * This listener is called when the portlet edit form (which tells information about the portlet width or height as well as
     * if the info bar and its content should be shown) is invoked.
     * <p/>
     * It places the form in the portal black mask
     */
    public static class EditPortletActionListener extends EventListener<UIPortlet> {
        public void execute(Event<UIPortlet> event) throws Exception {
            UIPortlet uiPortlet = event.getSource();
            UIPortalApplication uiApp = Util.getUIPortalApplication();
            UIMaskWorkspace uiMaskWS = uiApp.getChildById(UIPortalApplication.UI_MASK_WS_ID);
            UIPortletForm uiPortletForm = uiMaskWS.createUIComponent(UIPortletForm.class, null, null);

            if (uiPortletForm.setValues(uiPortlet) == false) {
                uiMaskWS.setUIComponent(null);
                WebuiRequestContext context = WebuiRequestContext.getCurrentInstance();
                context.getUIApplication().addMessage(
                        (new ApplicationMessage("UIPortlet.message.portletDeleted", null, ApplicationMessage.ERROR)));
            } else {
                uiMaskWS.setUpdated(true);
                uiMaskWS.setWindowSize(800, -1);
                event.getRequestContext().addUIComponentToUpdateByAjax(uiMaskWS);
            }
        }
    }

}
