/*
 * 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 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.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.SecurityErrorResponse;
import org.gatein.pc.api.invocation.response.SecurityResponse;
import org.gatein.pc.api.invocation.response.UpdateNavigationalStateResponse;

import javax.portlet.PortletMode;
import javax.portlet.WindowState;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.namespace.QName;
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;

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

   public static final String PORTLET_EVENTS = "PortletEvents";

   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
    */
   static public 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());
         }
         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
    */
   static public 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;

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

            //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);

            // todo: handle the error response better than this.
            if (!(portletResponse instanceof ContentResponse))
            {
               if (portletResponse instanceof ErrorResponse)
               {
                  ErrorResponse errorResponse = (ErrorResponse)portletResponse;
                  if (errorResponse.getCause() != null)
                  {
                     throw (Exception)errorResponse.getCause();
                  }
                  else if (errorResponse.getMessage() != null)
                  {
                     throw new Exception("Received an error response with message : " + errorResponse.getMessage());
                  }
                  else
                  {
                     throw new Exception("Received an error response.");
                  }
                  
               }
               else
               {
                  throw new Exception("Unexpected response type [" + portletResponse
                     + "]. Expected a ContentResponse or an ErrorResponse.");
               }
            }

            ContentResponse piResponse = (ContentResponse)portletResponse;

            //
            //Manage headers
            if (piResponse.getProperties() != null && piResponse.getProperties().getTransportHeaders() != null)
            {
               MultiValuedPropertyMap<String> transportHeaders = piResponse.getProperties().getTransportHeaders();
               Map<String, String> headers = new HashMap<String, String>();

               for (String key : transportHeaders.keySet())
               {
                  for (String value : transportHeaders.getValues(key))
                  {
                     headers.put(key, value);
                  }
               }
               context.setHeaders(headers);
            }

            String contentType = piResponse.getContentType();

            if (contentType == null)
            {
               return;
            }

            log.trace("Try to get a resource of type: " + contentType + " for the portlet: "
               + uiPortlet.getPortletContext());
            if (piResponse.getChars() != null || contentType.startsWith("text"))
            {
               context.getResponse().setContentType(contentType);
               context.getWriter().write(piResponse.getContent());
            }
            else
            {
               response.setContentType(contentType);
               if (piResponse.getBytes() != null)
               {
                  OutputStream stream = response.getOutputStream();
                  stream.write(piResponse.getBytes());
               }
               else
               {
                  if (piResponse.getChars() != null)
                  {
                     log.error("Received a content type of " + contentType + " but it contains no bytes of data. Chars were unexpectantly returned instead : " + piResponse.getChars());
                  }
                  else
                  {
                     log.error("Received a content type of " + contentType + " but it contains no bytes of data.");
                  }
               }


            }
            context.getResponse().flushBuffer();

         }
         catch (Exception e)
         {
            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);
         }
      }
   }

   /**
    * Process Events sent by the portlet API during the processAction() and serverResource() methods defined in Portlet
    * API 2.0 (JSR 286)
    */
   static public 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.setFullRender(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
    */
   static public 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
    */
   static public 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);
               }
            }
         }
      }

   }

   static public 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.setFullRender(true);

         String windowState = event.getRequestContext().getRequestParameter(Constants.PORTAL_WINDOW_STATE);
         if (windowState == null)
         {
            windowState = event.getRequestContext().getRequestParameter(UIComponent.OBJECTID).trim();
         }
         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. */
   static public class ChangePortletModeActionListener extends EventListener<UIPortlet>
   {
      public void execute(Event<UIPortlet> event) throws Exception
      {
         UIPortlet uiPortlet = event.getSource();
         String portletMode = event.getRequestContext().getRequestParameter(Constants.PORTAL_PORTLET_MODE);
         if (portletMode == null)
         {
            portletMode = event.getRequestContext().getRequestParameter(UIComponent.OBJECTID);
         }

         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
    */
   static public class EditPortletActionListener extends EventListener<UIPortlet>
   {
      public void execute(Event<UIPortlet> event) throws Exception
      {
         UIPortal uiPortal = Util.getUIPortal();
         UIPortalApplication uiApp = uiPortal.getAncestorOfType(UIPortalApplication.class);
         UIMaskWorkspace uiMaskWS = uiApp.getChildById(UIPortalApplication.UI_MASK_WS_ID);
         uiMaskWS.setUpdated(true);
         UIPortlet uiPortlet = event.getSource();
         UIPortletForm uiPortletForm = uiMaskWS.createUIComponent(UIPortletForm.class, null, null);
         uiPortletForm.setValues(uiPortlet);
         uiMaskWS.setWindowSize(800, -1);
         event.getRequestContext().addUIComponentToUpdateByAjax(uiMaskWS);
      }
   }

}
