/**
   * This method is called after an attribute is added to the ServletRequest. Note that this should
   * only get called for remote WSRP portlets. For more info, see:
   * http://issues.liferay.com/browse/FACES-146
   */
  public void attributeAdded(ServletRequestAttributeEvent servletRequestAttributeEvent) {

    // NOTE: We only care about phases prior to the RENDER_PHASE because we're concerned here about
    // managed beans
    // that get added to the request scope when the BridgeRequestScope begins. We're trying to
    // provide those managed
    // beans with an opportunity to prepare for an unexpected invocation of their methods annotated
    // with
    // @PreDestroy.
    ServletRequest servletRequest = servletRequestAttributeEvent.getServletRequest();
    PortletPhase phase = (PortletPhase) servletRequest.getAttribute(Bridge.PORTLET_LIFECYCLE_PHASE);

    // If this is taking place within a PortletRequest handled by the bridge in any phase prior to
    // the
    // RENDER_PHASE, then
    if ((phase != null) && (phase != PortletPhase.RENDER_PHASE)) {

      // If the attribute being added is not excluded, then invoke all methods on the attribute
      // value (class
      // instance) that are annotated with the BridgeRequestScopeAttributeAdded annotation.
      String attributeName = servletRequestAttributeEvent.getName();
      BridgeContext bridgeContext = BridgeContext.getCurrentInstance();
      BridgeConfig bridgeConfig = bridgeContext.getBridgeConfig();
      Set<String> excludedRequestScopeAttributes = bridgeConfig.getExcludedRequestAttributes();

      if (!excludedRequestScopeAttributes.contains(attributeName)) {

        Object attributeValue = servletRequestAttributeEvent.getValue();
        logger.trace("Attribute added name=[{0}] value=[{1}]", attributeName, attributeValue);

        if (attributeValue != null) {
          Method[] methods = attributeValue.getClass().getMethods();

          if (methods != null) {

            for (Method method : methods) {

              if (method != null) {

                if (method.isAnnotationPresent(BridgeRequestScopeAttributeAdded.class)) {

                  try {
                    method.invoke(attributeValue, new Object[] {});
                  } catch (Exception e) {
                    logger.error(e);
                  }
                }
              }
            }
          }
        }
      }
    }
  }
  /**
   * This method is called prior to the {@link PhaseId#RENDER_RESPONSE} phase of the JSF lifecycle.
   */
  @Override
  public void beforePhase(PhaseEvent phaseEvent) {

    // Determine if there are any resources in the LIFERAY_SHARED_PAGE_TOP request attribute, so
    // that execution of
    // the {@link #afterPhase(PhaseEvent)} can be optimized.
    liferaySharedPageTopLength = 0;

    BridgeContext bridgeContext = BridgeContext.getCurrentInstance();
    PortletRequest portletRequest = bridgeContext.getPortletRequest();
    StringBundler pageTop = getPageTop(portletRequest);

    if (pageTop != null) {
      liferaySharedPageTopLength = pageTop.length();
    }
  }
  /**
   * This method overrides the {@link #decode(FacesContext, UIComponent)} method so that it can
   * avoid a Servlet-API dependency in the RichFaces FileUploadRenderer. Note that rich:fileUpload
   * will do an Ajax postback and invoke the JSF lifecycle for each individual file.
   */
  @Override
  public void decode(FacesContext facesContext, UIComponent uiComponent) {

    try {

      // Get the UploadedFile from the request attribute map.
      ContextMapFactory contextMapFactory =
          (ContextMapFactory) FactoryExtensionFinder.getFactory(ContextMapFactory.class);
      BridgeContext bridgeContext = BridgeContext.getCurrentInstance();
      Map<String, Collection<UploadedFile>> uploadedFileMap =
          contextMapFactory.getUploadedFileMap(bridgeContext);

      if (uploadedFileMap != null) {

        // Use reflection to create a dynamic proxy class that implements the RichFaces UploadedFile
        // interface.
        Class<?> uploadedFileInterface = Class.forName(RICHFACES_UPLOADED_FILE_FQCN);
        Class<?> fileUploadEventClass = Class.forName(RICHFACES_FILE_UPLOAD_EVENT_FQCN);
        ClassLoader classLoader = uploadedFileInterface.getClassLoader();

        String clientId = uiComponent.getClientId(facesContext);
        Collection<UploadedFile> uploadedFiles = uploadedFileMap.get(clientId);

        if (uploadedFiles != null) {

          for (UploadedFile uploadedFile : uploadedFiles) {
            RichFacesUploadedFileHandler richFacesUploadedFileHandler =
                new RichFacesUploadedFileHandler(uploadedFile);
            Object richFacesUploadedFile =
                Proxy.newProxyInstance(
                    classLoader, new Class[] {uploadedFileInterface}, richFacesUploadedFileHandler);
            FacesEvent fileUploadEvent =
                (FacesEvent)
                    fileUploadEventClass
                        .getConstructor(UIComponent.class, uploadedFileInterface)
                        .newInstance(uiComponent, richFacesUploadedFile);

            // Queue the RichFaces FileUploadEvent instance so that it can be handled with an
            // ActionListener.
            uiComponent.queueEvent(fileUploadEvent);
          }
        }
      }
    } catch (Exception e) {
      logger.error(e);
    }
  }
  public ExternalContextCompat_1_2_Impl(
      PortletContext portletContext,
      PortletRequest portletRequest,
      PortletResponse portletResponse) {

    this.portletContext = portletContext;
    this.portletRequest = portletRequest;
    this.portletResponse = portletResponse;

    // Get the BridgeContext.
    this.bridgeContext = BridgeContext.getCurrentInstance();

    this.incongruityContext = bridgeContext.getIncongruityContext();

    // Determine whether or not lifecycle incongruities should be managed.
    PortletConfig portletConfig = bridgeContext.getPortletConfig();
    this.manageIncongruities =
        PortletConfigParam.ManageIncongruities.getBooleanValue(portletConfig);
  }
  /** This method is called after the {@link PhaseId#RENDER_RESPONSE} phase of the JSF lifecycle. */
  @Override
  public void afterPhase(PhaseEvent phaseEvent) {

    BridgeContext bridgeContext = BridgeContext.getCurrentInstance();

    // Remove duplicate resources from the LIFERAY_SHARED_PAGE_TOP request attribute. For more
    // information, see:
    // http://issues.liferay.com/browse/FACES-1216
    if (liferaySharedPageTopLength > 0) {

      PortletRequest portletRequest = bridgeContext.getPortletRequest();

      StringBundler pageTop = getPageTop(portletRequest);

      if (pageTop != null) {

        LiferaySharedPageTop liferaySharedPageTop = new LiferaySharedPageTop(pageTop);
        liferaySharedPageTop.removeDuplicates();
        pageTop = liferaySharedPageTop.toStringBundler();

        setPageTop(portletRequest, pageTop);
      }
    }
  }
  /**
   * Saves the state of the FacesContext as required by section 5.1.2 of the JSR 329 spec. This
   * method is designed to be called during the ACTION_PHASE of the portlet lifecycle.
   *
   * @param facesContext The current faces context.
   */
  public void saveState(FacesContext facesContext) {

    logger.debug("saveState(facesContext)");

    // Get the ExternalContext and PortletResponse.
    BridgeContext bridgeContext = BridgeContext.getCurrentInstance();
    ExternalContext externalContext = facesContext.getExternalContext();
    PortletResponse portletResponse =
        (PortletResponse) facesContext.getExternalContext().getResponse();

    if ((beganInPhase == Bridge.PortletPhase.ACTION_PHASE)
        || (beganInPhase == Bridge.PortletPhase.EVENT_PHASE)
        || (beganInPhase == Bridge.PortletPhase.RESOURCE_PHASE)) {

      // Save the view root.
      setAttribute(BRIDGE_REQ_SCOPE_ATTR_FACES_VIEW_ROOT, facesContext.getViewRoot());

      // If the PortletMode hasn't changed, then preserve the "javax.faces.ViewState" request
      // parameter value.
      if (!isPortletModeChanged()) {

        if (portletResponse instanceof ActionResponse) {
          String viewState =
              facesContext
                  .getExternalContext()
                  .getRequestParameterMap()
                  .get(ResponseStateManager.VIEW_STATE_PARAM);

          if (viewState != null) {

            // NOTE: Although it is possible to save this as a render parameter, can't use that
            // approach
            // because portlet containers like Pluto will add the "javax.faces.ViewState" parameter
            // to any
            // ResourceURLs that are created during the RENDER_PHASE of the portlet lifecycle.
            setAttribute(ResponseStateManager.VIEW_STATE_PARAM, viewState);
          }
        }
      }

      // If specified in the WEB-INF/portlet.xml descriptor, then preserve the action parameters.
      if (bridgeContext.isPreserveActionParams()) {
        Map<String, String> actionRequestParameterMap =
            new HashMap<String, String>(externalContext.getRequestParameterMap());
        actionRequestParameterMap.remove(ResponseStateManager.VIEW_STATE_PARAM);
        actionRequestParameterMap.remove(JAVAX_FACES_ENCODED_URL_PARAM);
        setAttribute(BRIDGE_REQ_SCOPE_ATTR_ACTION_PARAMS, actionRequestParameterMap);
      }

      // Save the list of faces messages.
      List<FacesMessageWrapper> facesMessageWrappers = new ArrayList<FacesMessageWrapper>();
      Iterator<String> clientIds = facesContext.getClientIdsWithMessages();

      while (clientIds.hasNext()) {
        String clientId = clientIds.next();
        Iterator<FacesMessage> facesMessages = facesContext.getMessages(clientId);

        while (facesMessages.hasNext()) {
          FacesMessage facesMessage = facesMessages.next();
          FacesMessageWrapper facesMessageWrapper = new FacesMessageWrapper(clientId, facesMessage);
          facesMessageWrappers.add(facesMessageWrapper);
        }
      }

      if (facesMessageWrappers.size() > 0) {
        setAttribute(BRIDGE_REQ_SCOPE_ATTR_FACES_MESSAGES, facesMessageWrappers);
      } else {
        logger.trace("Not saving any faces messages");
      }

      // NOTE: PROPOSED-FOR-BRIDGE3-API: https://issues.apache.org/jira/browse/PORTLETBRIDGE-203
      // Build up a list
      // of attributes found in the FacesContext attribute map and save them. It has to be copied in
      // this manner
      // because the Faces implementation likely calls the clear() method during the call to its
      // FacesContextImpl.release() method.
      saveJSF2FacesContextAttributes(facesContext);
    }

    if ((beganInPhase == Bridge.PortletPhase.ACTION_PHASE)
        || (beganInPhase == Bridge.PortletPhase.EVENT_PHASE)
        || (beganInPhase == Bridge.PortletPhase.RESOURCE_PHASE)) {

      boolean saveNonExcludedAttributes = true;

      // If a redirect occurred, then indicate that the non-excluded request attributes are not to
      // be preserved.
      if (isRedirectOccurred()) {

        // TCK TestPage062: eventScopeNotRestoredRedirectTest
        logger.trace("Due to redirect, not saving any non-excluded request attributes");
        saveNonExcludedAttributes = false;
      }

      // Otherwise, if the portlet mode has changed, then indicate that the non-exluded request
      // attributes are
      // not to be preserved.
      else if (isPortletModeChanged()) {
        logger.trace("Due to PortletMode change, not saving any non-excluded request attributes");
        saveNonExcludedAttributes = false;
      }

      // If appropriate, save the non-excluded request attributes. This would include, for example,
      // managed-bean
      // instances that may have been created during the ACTION_PHASE that need to survive to the
      // RENDER_PHASE.
      Map<String, Object> currentRequestAttributes = externalContext.getRequestMap();

      if (currentRequestAttributes != null) {
        List<RequestAttribute> savedRequestAttributes = new ArrayList<RequestAttribute>();
        List<String> nonExcludedAttributeNames = new ArrayList<String>();
        Iterator<Map.Entry<String, Object>> itr = currentRequestAttributes.entrySet().iterator();

        if (itr != null) {

          while (itr.hasNext()) {
            Map.Entry<String, Object> mapEntry = itr.next();
            String attributeName = mapEntry.getKey();
            Object attributeValue = mapEntry.getValue();

            if (isExcludedRequestAttributeByConfig(attributeName, attributeValue)
                || isExcludedRequestAttributeByAnnotation(attributeValue)
                || isExcludedRequestAttributeByNamespace(attributeName)
                || isExcludedRequestAttributeByInstance(attributeName, attributeValue)
                || isExcludedRequestAttributeByPreExisting(attributeName)) {

              logger.trace("NOT saving EXCLUDED attribute name=[{0}]", attributeName);
            } else {

              if (saveNonExcludedAttributes) {
                logger.trace(
                    "SAVING non-excluded request attribute name=[{0}] value=[{1}]",
                    attributeName, attributeValue);
                savedRequestAttributes.add(new RequestAttribute(attributeName, attributeValue));
              }

              nonExcludedAttributeNames.add(attributeName);
            }
          }

          if (savedRequestAttributes.size() > 0) {
            setAttribute(BRIDGE_REQ_SCOPE_ATTR_REQUEST_ATTRIBUTES, savedRequestAttributes);
          } else {
            logger.trace("Not saving any non-excluded request attributes");
          }

          setAttribute(BRIDGE_REQ_SCOPE_NON_EXCLUDED_ATTR_NAMES, nonExcludedAttributeNames);
        }
      } else {
        logger.trace(
            "Not saving any non-excluded request attributes because there are no request attributes!");
      }
    }

    // If running in the ACTION_PHASE or EVENT_PHASE, then the Flash scope must be saved as well so
    // that it can be
    // restored.
    Bridge.PortletPhase portletRequestPhase = bridgeContext.getPortletRequestPhase();

    if ((portletRequestPhase == Bridge.PortletPhase.ACTION_PHASE)
        || (portletRequestPhase == Bridge.PortletPhase.EVENT_PHASE)) {

      // PROPOSED-FOR-JSR344-API: http://java.net/jira/browse/JAVASERVERFACES_SPEC_PUBLIC-1070
      // PROPOSED-FOR-BRIDGE3-API: https://issues.apache.org/jira/browse/PORTLETBRIDGE-201
      saveFlashState(facesContext);
    }

    // If running in the ACTION_PHASE or EVENT_PHASE, then the incongruity context must be saved as
    // well so that it
    // can be restored.
    if ((portletRequestPhase == Bridge.PortletPhase.ACTION_PHASE)
        || (portletRequestPhase == Bridge.PortletPhase.EVENT_PHASE)) {

      IncongruityContext incongruityContext = bridgeContext.getIncongruityContext();
      Map<String, Object> incongruityAttributeMap = incongruityContext.getAttributes();
      int mapSize = incongruityAttributeMap.size();
      List<IncongruityAttribute> savedIncongruityAttributes =
          new ArrayList<IncongruityAttribute>(mapSize);
      Iterator<Map.Entry<String, Object>> itr = incongruityAttributeMap.entrySet().iterator();

      while (itr.hasNext()) {
        Map.Entry<String, Object> mapEntry = itr.next();
        String name = mapEntry.getKey();
        Object value = mapEntry.getValue();
        logger.trace("Saving IncongruityContext attribute name=[{0}] value=[{1}]", name, value);
        savedIncongruityAttributes.add(new IncongruityAttribute(name, value));
      }

      setAttribute(
          BRIDGE_REQ_SCOPE_ATTR_INCONGRUITY_CONTEXT_ATTRIBUTES, savedIncongruityAttributes);
    }
  }
  @SuppressWarnings("unchecked")
  public void restoreState(FacesContext facesContext) {

    logger.debug("restoreState(facesContext)");

    boolean restoreNonExcludedRequestAttributes =
        ((beganInPhase == Bridge.PortletPhase.ACTION_PHASE)
            || (beganInPhase == Bridge.PortletPhase.EVENT_PHASE)
            || (beganInPhase == Bridge.PortletPhase.RESOURCE_PHASE));

    BridgeContext bridgeContext = BridgeContext.getCurrentInstance();

    PortletPhase portletRequestPhase = bridgeContext.getPortletRequestPhase();

    if (portletRequestPhase == Bridge.PortletPhase.RENDER_PHASE) {

      if (!portletMode.equals(bridgeContext.getPortletRequest().getPortletMode())) {
        setPortletModeChanged(true);
        restoreNonExcludedRequestAttributes = false;
      }
    }

    if ((beganInPhase == Bridge.PortletPhase.ACTION_PHASE)
        || (beganInPhase == Bridge.PortletPhase.EVENT_PHASE)
        || (beganInPhase == Bridge.PortletPhase.RESOURCE_PHASE)) {

      // Restore the view root that may have been saved during the ACTION_PHASE of the portlet
      // lifecycle.
      UIViewRoot uiViewRoot = (UIViewRoot) getAttribute(BRIDGE_REQ_SCOPE_ATTR_FACES_VIEW_ROOT);

      if (uiViewRoot != null) {
        facesContext.setViewRoot(uiViewRoot);
        logger.debug("Restored viewId=[{0}] uiViewRoot=[{1}]", uiViewRoot.getViewId(), uiViewRoot);
      } else {
        logger.debug("Did not restore uiViewRoot");
      }

      // Restore the faces messages that may have been saved during the ACTION_PHASE of the portlet
      // lifecycle.
      List<FacesMessageWrapper> facesMessages =
          (List<FacesMessageWrapper>) getAttribute(BRIDGE_REQ_SCOPE_ATTR_FACES_MESSAGES);

      boolean restoredFacesMessages = false;

      if (facesMessages != null) {

        for (FacesMessageWrapper facesMessageWrapper : facesMessages) {
          String clientId = facesMessageWrapper.getClientId();
          FacesMessage facesMessage = facesMessageWrapper.getFacesMessage();
          facesContext.addMessage(clientId, facesMessage);
          logger.trace("Restored facesMessage=[{0}]", facesMessage.getSummary());
          restoredFacesMessages = true;
        }
      }

      if (restoredFacesMessages) {
        logger.debug("Restored facesMessages");
      } else {
        logger.debug("Did not restore any facesMessages");
      }

      // NOTE: PROPOSE-FOR-BRIDGE3-API: https://issues.apache.org/jira/browse/PORTLETBRIDGE-203
      // Restore the
      // FacesContext attributes that may have been saved during the ACTION_PHASE of the portlet
      // lifecycle.
      restoreJSF2FacesContextAttributes(facesContext);
    }

    if (restoreNonExcludedRequestAttributes) {

      // Restore the non-excluded request attributes.
      List<RequestAttribute> savedRequestAttributes =
          (List<RequestAttribute>) getAttribute(BRIDGE_REQ_SCOPE_ATTR_REQUEST_ATTRIBUTES);

      boolean restoredNonExcludedRequestAttributes = false;

      if (savedRequestAttributes != null) {
        Map<String, Object> currentRequestAttributes =
            facesContext.getExternalContext().getRequestMap();

        // If a redirect did not occur, then restore the non-excluded request attributes.
        if (!isRedirectOccurred()) {

          for (RequestAttribute requestAttribute : savedRequestAttributes) {
            String name = requestAttribute.getName();
            Object value = requestAttribute.getValue();
            logger.trace(
                "Restoring non-excluded request attribute name=[{0}] value=[{1}]", name, value);
            currentRequestAttributes.put(name, value);
            restoredNonExcludedRequestAttributes = true;
          }
        }
      }

      if (restoredNonExcludedRequestAttributes) {
        logger.debug("Restored non-excluded request attributes");
      } else {
        logger.debug("Did not restore any non-excluded request attributes");
      }
    }

    // If running in the RENDER_PHASE, then the Flash scope must be restored.
    if (portletRequestPhase == Bridge.PortletPhase.RENDER_PHASE) {

      // NOTE: PROPOSED-FOR-BRIDGE3-API: https://issues.apache.org/jira/browse/PORTLETBRIDGE-201
      // Restore the flash scope.
      restoreFlashState(facesContext);
    }

    // If running in the RENDER_PHASE, then the incongruity context must be restored.
    if (((beganInPhase == Bridge.PortletPhase.ACTION_PHASE)
            || (beganInPhase == Bridge.PortletPhase.EVENT_PHASE))
        && (portletRequestPhase == Bridge.PortletPhase.RENDER_PHASE)) {

      List<IncongruityAttribute> savedIncongruityAttributes =
          (List<IncongruityAttribute>)
              getAttribute(BRIDGE_REQ_SCOPE_ATTR_INCONGRUITY_CONTEXT_ATTRIBUTES);

      if (savedIncongruityAttributes != null) {

        IncongruityContext incongruityContext = bridgeContext.getIncongruityContext();
        Map<String, Object> incongruityContextAttributes = incongruityContext.getAttributes();

        for (IncongruityAttribute incongruityAttribute : savedIncongruityAttributes) {
          String key = incongruityAttribute.getName();
          Object value = incongruityAttribute.getValue();
          incongruityContextAttributes.put(key, value);
        }
      }
    }
  }