/** @author Neil Griffin */
@ManagedBean(name = "customersModelBean")
@RequestScoped
public class CustomersModelBean implements Serializable {

  // serialVersionUID
  private static final long serialVersionUID = 2241487919972557504L;

  // Logger
  private static final Logger logger = LoggerFactory.getLogger(CustomersModelBean.class);

  // Injections
  @ManagedProperty(name = "customerService", value = "#{customerService}")
  private CustomerService customerService;

  // Private Bean Properties
  private List<Customer> allCustomers;
  private String selectedCustomerId;

  @PostConstruct
  public void postConstruct() {
    logger.trace("@PostConstruct annotation worked");
    allCustomers = customerService.getAllCustomers();
  }

  @PreDestroy
  public void preDestroy() {
    logger.trace("@PreDestroy annotation worked");
  }

  public List<Customer> getAllCustomers() {
    return allCustomers;
  }

  public void setCustomerService(CustomerService customerService) {

    // Injected via ManagedProperty annotation
    this.customerService = customerService;
  }

  public String getSelectedCustomerId() {
    return selectedCustomerId;
  }

  public void setSelectedCustomerId(String selectedCustomerId) {
    this.selectedCustomerId = selectedCustomerId;
  }
}
/** @author Neil Griffin */
@ManagedBean(name = "docLibBackingBean")
@RequestScoped
public class DocLibBackingBean {

  // Logger
  private static final Logger logger = LoggerFactory.getLogger(DocLibBackingBean.class);

  // Self-Injections
  private LiferayFacesContext liferayFacesContext = LiferayFacesContext.getInstance();

  // Injections
  @ManagedProperty(name = "docLibModelBean", value = "#{docLibModelBean}")
  private DocLibModelBean docLibModelBean;

  @ManagedProperty(name = "docLibViewBean", value = "#{docLibViewBean}")
  private DocLibViewBean docLibViewBean;

  // Private Data Members
  private String folderName;
  private String folderDescription;
  private Boolean permittedToAddDocument;
  private Boolean permittedToAddFolder;

  // Action Listeners
  private AddFolderActionListener addFolderActionListener = new AddFolderActionListener();
  private PopDownActionListener popDownActionListener = new PopDownActionListener();
  private PopUpActionListener popUpActionListener = new PopUpActionListener();

  public void treeNodeSelected(ActionEvent actionEvent) {

    FacesContext facesContext = FacesContext.getCurrentInstance();
    ExternalContext externalContext = facesContext.getExternalContext();
    long folderId = LongHelper.toLong(externalContext.getRequestParameterMap().get("folderId"), 0L);
    FolderTreeNode folderTreeNode =
        docLibModelBean.getFolderTreeModel().findFolderTreeNode(folderId);
    FolderUserObject folderUserObject = folderTreeNode.getFolderUserObject();
    docLibModelBean.setSelectedUserObject(folderUserObject);
    permittedToAddFolder = null;
    permittedToAddDocument = null;
  }

  public AddFolderActionListener getAddFolderActionListener() {
    return addFolderActionListener;
  }

  public void setDocLibModelBean(DocLibModelBean docLibModelBean) {

    // Injected via ManagedProperty annotation
    this.docLibModelBean = docLibModelBean;
  }

  public void setDocLibViewBean(DocLibViewBean docLibViewBean) {

    // Injected via ManagedProperty annotation.
    this.docLibViewBean = docLibViewBean;
  }

  public String getFolderDescription() {
    return folderDescription;
  }

  public void setFolderDescription(String folderDescription) {
    this.folderDescription = folderDescription;
  }

  public String getFolderName() {
    return folderName;
  }

  public void setFolderName(String folderName) {
    this.folderName = folderName;
  }

  public PopDownActionListener getPopDownActionListener() {
    return popDownActionListener;
  }

  public PopUpActionListener getPopUpActionListener() {
    return popUpActionListener;
  }

  public boolean isPermittedToAddFolder() {

    if (permittedToAddFolder == null) {

      try {
        PermissionChecker permissionChecker = liferayFacesContext.getPermissionChecker();
        DLFolder selectedDLFolder = docLibModelBean.getSelectedFolderUserObject().getDlFolder();
        long scopeGroupId = selectedDLFolder.getGroupId();
        long folderId = selectedDLFolder.getFolderId();
        permittedToAddFolder =
            DLFolderPermission.contains(
                permissionChecker, scopeGroupId, folderId, ActionKeys.ADD_FOLDER);
      } catch (Exception e) {
        logger.error(e.getMessage(), e);
      }
    }

    return permittedToAddFolder;
  }

  public boolean isPermittedToAddDocument() {

    if (permittedToAddDocument == null) {

      try {
        PermissionChecker permissionChecker = liferayFacesContext.getPermissionChecker();
        DLFolder selectedDLFolder = docLibModelBean.getSelectedFolderUserObject().getDlFolder();
        long scopeGroupId = selectedDLFolder.getGroupId();
        long folderId = selectedDLFolder.getFolderId();
        permittedToAddDocument =
            DLFolderPermission.contains(
                permissionChecker, scopeGroupId, folderId, ActionKeys.ADD_DOCUMENT);
        permittedToAddFolder =
            DLFolderPermission.contains(
                permissionChecker, scopeGroupId, folderId, ActionKeys.ADD_FOLDER);
      } catch (Exception e) {
        logger.error(e.getMessage(), e);
      }
    }

    return permittedToAddDocument;
  }

  protected class AddFolderActionListener implements ActionListener {
    public void processAction(ActionEvent actionEvent) throws AbortProcessingException {

      try {
        FolderUserObject folderUserObject = docLibModelBean.getSelectedFolderUserObject();
        DLFolder dlFolder = folderUserObject.getDlFolder();
        long groupId = dlFolder.getGroupId();
        long repositoryId = dlFolder.getRepositoryId();
        boolean mountPoint = dlFolder.getMountPoint();
        long parentFolderId = dlFolder.getFolderId();
        ServiceContext serviceContext = new ServiceContext();

        // Temporary: Make the default setting be that community members can view the file. Need to
        // develop a
        // "Viewable By" permissions Facelet composite component UI similar to
        // portal-web/docroot/html/taglib/ui/input_permissions/page.jsp
        serviceContext.setAddGroupPermissions(true);
        DLFolderServiceUtil.addFolder(
            groupId,
            repositoryId,
            mountPoint,
            parentFolderId,
            folderName,
            folderDescription,
            serviceContext);
        docLibModelBean.forceTreeRequery();
        logger.debug("Added folderName=[{0}] description=[{1}]", folderName, folderDescription);
      } catch (Exception e) {
        logger.error(e.getMessage(), e);
        liferayFacesContext.addGlobalUnexpectedErrorMessage();
      }

      docLibViewBean.setPopupRendered(false);
    }
  }

  protected class PopDownActionListener implements ActionListener {

    public void processAction(ActionEvent actionEvent) throws AbortProcessingException {
      docLibViewBean.setPopupRendered(false);
    }
  }

  protected class PopUpActionListener implements ActionListener {

    public void processAction(ActionEvent actionEvent) throws AbortProcessingException {
      docLibViewBean.setPopupRendered(true);
    }
  }
}
/**
 * This class is a JSF {@link PhaseListener} that listens to the {@link PhaseId#INVOKE_APPLICATION}
 * and {@link PhaseId#RENDER_RESPONSE} phases of the JSF lifecycle. Along with {@link
 * HeadManagedBean} and {@link HeadRendererBridgeImpl}, this class helps provides a solution to an
 * issue regarding Ajax-initiated execution of navigation-rules in a portlet. When a portal page is
 * first rendered by the portal, all of the portlets on the page participate in the {@link
 * PortletRequest#RENDER_PHASE} of the Portlet lifecycle. During this initial HTTP-GET operation,
 * the bridge has the ability to add JavaScript and CSS resources to the &lt;head&gt; section of the
 * rendered portal page. Subsequent Ajax-initiated execution of the JSF lifecycle via the {@link
 * PortletRequest#RESOURCE_PHASE} are NOT ABLE add resources to the to the &lt;head&gt; section.
 *
 * @see http://issues.liferay.com/browse/FACES-180
 * @author Neil Griffin
 */
public class HeadPhaseListener implements PhaseListener {

  // serialVersionUID
  private static final long serialVersionUID = 8502242430265622811L;

  // Logger
  private static final Logger logger = LoggerFactory.getLogger(HeadPhaseListener.class);

  public void afterPhase(PhaseEvent phaseEvent) {

    // This method just does some logging. It's useful to the developer to determine if a
    // navigation-rule
    // fired, causing new JSF view to be restored after the INVOKE_APPLICATION phase finished.
    if (logger.isDebugEnabled() && (phaseEvent.getPhaseId() == PhaseId.INVOKE_APPLICATION)) {
      FacesContext facesContext = phaseEvent.getFacesContext();
      String viewId = facesContext.getViewRoot().getViewId();
      logger.debug("After INVOKE_APPLICATION: viewId=[{0}]", viewId);
    }
  }

  public void beforePhase(PhaseEvent phaseEvent) {

    Bridge.PortletPhase portletRequestPhase = BridgeUtil.getPortletRequestPhase();

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

      // If about to execute the INVOKE_APPLICATION phase of the JSF lifecycle, then
      if (phaseEvent.getPhaseId() == PhaseId.INVOKE_APPLICATION) {
        beforeInvokeApplicationPhase(phaseEvent);
      } else if (phaseEvent.getPhaseId() == PhaseId.RENDER_RESPONSE) {
        beforeRenderResponsePhase(phaseEvent);
      }
    }
  }

  /**
   * This method is called before the {@link PhaseId#INVOKE_APPLICATION} phase of the JSF lifecycle
   * is executed. The purpose of this timing is to handle the case when the user clicks on a {@link
   * UICommand} component (like h:commandButton or h:commandLink) that has been either
   * Auto-ajaxified by ICEfaces, or manually Ajaxified by the developer using code like the
   * following:
   *
   * <p><code>&lt;f:ajax execute="@form" render=" @form" /&gt;</code>
   *
   * <p>When this happens, we need to somehow remember the list of JavaScript and/or CSS resources
   * that are currently in the &lt;head&gt; section of the portal page. This is because a
   * navigation-rule might fire which could cause a new view to be rendered in the {@link
   * PhaseId#RENDER_RESPONSE} phase that is about to follow this {@link PhaseId#INVOKE_APPLICATION}
   * phase. The list of resources would be contained in the {@link HeadManagedBean} {@link
   * ViewScoped} instance that is managed by the JSF managed-bean facility. The list would have been
   * populated initially in the {@link HeadManagedBean} by the {@link HeadRender} during the initial
   * HTTP-GET of the portal page. The way we "remember" the list is by placing it into the JSF 2
   * {@link Flash} scope. This scope is used because it is very short-lived and survives any
   * navigation-rules that might fire, thereby causing the rendering of a new JSF view.
   *
   * <p>The story is continued in the {@link #beforeRenderResponsePhase(PhaseEvent)} method below...
   */
  protected void beforeInvokeApplicationPhase(PhaseEvent phaseEvent) {

    // Get the list of resourceIds that might be contained in the Flash scope. Note that they would
    // have been
    // placed into the Flash scope by this very same method, except during in the case below for the
    // RENDER_RESPONSE phase.
    FacesContext facesContext = phaseEvent.getFacesContext();
    Flash flash = facesContext.getExternalContext().getFlash();

    @SuppressWarnings("unchecked")
    Set<String> headResourceIdsFromFlash = (Set<String>) flash.get("HEAD_RESOURCE_IDS");

    // Log the viewId so that it can be visually compared with the value that is to be logged after
    // the
    // INVOKE_APPLICATION phase completes.
    logger.debug("Before INVOKE_APPLICATION: viewId=[{0}]", facesContext.getViewRoot().getViewId());

    // If the Flash scope does not yet contain a list of head resourceIds, then the scope needs to
    // be populated
    // with a list so that the {@link #beforeRenderResponsePhase(PhaseEvent)} method below can
    // retrieve it.
    if (headResourceIdsFromFlash == null) {

      HeadManagedBean headManagedBean = HeadManagedBean.getInstance(facesContext);

      // Note that in the case where a portlet RESOURCE_PHASE was invoked with a "portlet:resource"
      // type of URL,
      // there will be no HeadManagedBean available.
      if (headManagedBean != null) {
        flash.put("HEAD_RESOURCE_IDS", headManagedBean.getHeadResourceIds());
      }
    }
  }

  /**
   * This method is called before the {@link PhaseId#RENDER_RESPONSE} phase of the JSF lifecycle is
   * executed. The purpose of this timing is to pick up where the {@link
   * #beforeInvokeApplicationPhase(PhaseEvent)} method left off. It might be the case that a
   * navigation-rule has fired and a NEW JSF view has been loaded up after the INVOKE_APPLICATION
   * phase completed. If this is the case, then the list of head resourceIds in the {@link
   * HeadManagedBean} needs to be repopulated from the list found in the Flash scope.
   */
  protected void beforeRenderResponsePhase(PhaseEvent phaseEvent) {
    FacesContext facesContext = phaseEvent.getFacesContext();
    Flash flash = facesContext.getExternalContext().getFlash();
    String viewId = facesContext.getViewRoot().getViewId();

    @SuppressWarnings("unchecked")
    Set<String> headResourceIdsFromFlash = (Set<String>) flash.get("HEAD_RESOURCE_IDS");

    if (headResourceIdsFromFlash != null) {
      HeadManagedBean headManagedBean = HeadManagedBean.getInstance(facesContext);
      Set<String> managedBeanResourceIds = headManagedBean.getHeadResourceIds();

      for (String resourceIdFromFlash : headResourceIdsFromFlash) {

        if (!managedBeanResourceIds.contains(resourceIdFromFlash)) {
          managedBeanResourceIds.add(resourceIdFromFlash);
          logger.debug(
              "Added resourceId=[{0}] from the Flash scope to the list of resourceIds in the HeadManagedBean for viewId=[{1}]",
              resourceIdFromFlash, viewId);
        }
      }
    }
  }

  public PhaseId getPhaseId() {
    return PhaseId.ANY_PHASE;
  }
}
/** @author Neil Griffin */
public class BridgeRequestScopeImpl extends ConcurrentHashMap<String, Object>
    implements BridgeRequestScope {

  // serialVersionUID
  private static final long serialVersionUID = 7113251688518329851L;

  // Logger
  private static final Logger logger = LoggerFactory.getLogger(BridgeRequestScopeImpl.class);

  // Private Constants for Bridge Request Scope Attributes
  private static final String BRIDGE_REQ_SCOPE_ATTR_ACTION_PARAMS =
      "com.liferay.faces.bridge.actionParams";
  private static final String BRIDGE_REQ_SCOPE_ATTR_FACES_CONTEXT_ATTRIBUTES =
      "com.liferay.faces.bridge.facescontext.attributes";
  private static final String BRIDGE_REQ_SCOPE_ATTR_FACES_MESSAGES =
      "com.liferay.faces.bridge.faces.messages";
  private static final String BRIDGE_REQ_SCOPE_ATTR_FACES_VIEW_ROOT =
      "com.liferay.faces.bridge.faces.view.root";
  private static final String BRIDGE_REQ_SCOPE_ATTR_REQUEST_ATTRIBUTES =
      "com.liferay.faces.bridge.faces.request.attributes";

  // Private Constants for EXCLUDED namespaces listed in Section 5.1.2 of the JSR 329 Spec
  private static final String EXCLUDED_NAMESPACE_JAVAX_FACES = "javax.faces";
  private static final String EXCLUDED_NAMESPACE_JAVAX_PORTLET = "javax.portlet";
  private static final String EXCLUDED_NAMESPACE_JAVAX_PORTLET_FACES = "javax.portlet.faces";
  private static final String EXCLUCED_NAMESPACE_JAVAX_SERVLET = "javax.servlet";
  private static final String EXCLUCED_NAMESPACE_JAVAX_SERVLET_INCLUDE = "javax.servlet.include";
  private static List<String> STANDARD_EXCLUDED_REQUEST_ATTRIBUTE_NAMESPACES =
      new ArrayList<String>(3);

  // Other Private Constants
  private static final String JAVAX_FACES_ENCODED_URL_PARAM = "javax.faces.encodedURL";

  static {

    // Build up the static list of standard excluded request attribute namespaces.
    STANDARD_EXCLUDED_REQUEST_ATTRIBUTE_NAMESPACES.add(EXCLUDED_NAMESPACE_JAVAX_FACES);
    STANDARD_EXCLUDED_REQUEST_ATTRIBUTE_NAMESPACES.add(EXCLUDED_NAMESPACE_JAVAX_PORTLET);
    STANDARD_EXCLUDED_REQUEST_ATTRIBUTE_NAMESPACES.add(EXCLUDED_NAMESPACE_JAVAX_PORTLET_FACES);
    STANDARD_EXCLUDED_REQUEST_ATTRIBUTE_NAMESPACES.add(EXCLUCED_NAMESPACE_JAVAX_SERVLET);
    STANDARD_EXCLUDED_REQUEST_ATTRIBUTE_NAMESPACES.add(EXCLUCED_NAMESPACE_JAVAX_SERVLET_INCLUDE);
  }

  // Private Data Members
  private Map<String, Object> attributeMap;
  private boolean beganInActionOrEventRequest;
  private List<String> excludedAttributeNames;
  private Flash flash;
  private String idPrefix;
  private String idSuffix;
  private Map<String, Object> managedBeanMap;
  private PortletMode portletMode;
  private boolean portletModeChanged;
  private Set<String> preExistingAttributeNames;
  private boolean redirect;

  public BridgeRequestScopeImpl(
      PortletConfig portletConfig,
      PortletContext portletContext,
      PortletRequest portletRequest,
      String idPrefix) {

    this.attributeMap = new HashMap<String, Object>();

    long timeInMillis = Calendar.getInstance().getTimeInMillis();
    this.idPrefix = idPrefix;
    this.idSuffix = Long.toString(timeInMillis);

    BridgeConfigFactory bridgeConfigFactory =
        (BridgeConfigFactory) BridgeFactoryFinder.getFactory(BridgeConfigFactory.class);
    BridgeConfig bridgeConfig = bridgeConfigFactory.getBridgeConfig();
    this.excludedAttributeNames = new ArrayList<String>();

    // Get the list of excluded BridgeRequestScope attributes from the faces-config.xml descriptors.
    Set<String> facesConfigExcludedAttributeNames = bridgeConfig.getExcludedRequestAttributes();

    // Get the list of excluded BridgeRequestScope attributes from the WEB-INF/portlet.xml
    // descriptor.
    @SuppressWarnings("unchecked")
    List<String> portletContextExcludedAttributeNames =
        (List<String>)
            portletContext.getAttribute(
                Bridge.BRIDGE_PACKAGE_PREFIX
                    + portletConfig.getPortletName()
                    + BridgeConstants.CHAR_PERIOD
                    + Bridge.EXCLUDED_REQUEST_ATTRIBUTES);

    // Combine the two lists into a single list of excluded BridgeRequestScope attributes.
    if (facesConfigExcludedAttributeNames != null) {
      this.excludedAttributeNames.addAll(facesConfigExcludedAttributeNames);
    }

    if (portletContextExcludedAttributeNames != null) {
      this.excludedAttributeNames.addAll(portletContextExcludedAttributeNames);
    }

    this.preExistingAttributeNames = getPreExistingRequestAttributeNames(portletRequest);

    Bridge.PortletPhase portletPhase =
        (Bridge.PortletPhase) portletRequest.getAttribute(Bridge.PORTLET_LIFECYCLE_PHASE);

    if ((portletPhase == Bridge.PortletPhase.ACTION_PHASE)
        || (portletPhase == Bridge.PortletPhase.EVENT_PHASE)) {
      beganInActionOrEventRequest = true;
    }
  }

  /**
   * The overrides for {@link #toString()} and {@link #hashCode()} are necessary because the {@link
   * ConcurrentHashMap} parent class overrides them and causes debug logs to be difficult to
   * interpret.
   */
  @Override
  public int hashCode() {
    return System.identityHashCode(this);
  }

  /**
   * 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 preserveScopedData(FacesContext facesContext) {

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

    // Get the ExternalContext.
    ExternalContext externalContext = facesContext.getExternalContext();

    // 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 (!portletModeChanged) {
      PortletResponse portletResponse =
          (PortletResponse) facesContext.getExternalContext().getResponse();

      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.
    BridgeContext bridgeContext =
        (BridgeContext) facesContext.getAttributes().get(BridgeExt.BRIDGE_CONTEXT_ATTRIBUTE);

    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");
    }

    // 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.
    if ((!redirect) && (!portletModeChanged)) {
      Map<String, Object> currentRequestAttributes = externalContext.getRequestMap();

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

        if (itr != null) {

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

            if (isExcludedRequestAttribute(name, value)) {
              logger.trace("Not saving EXCLUDED attribute name=[{0}]", name);
            } else if ((value != null)
                && (value.getClass().getAnnotation(ExcludeFromManagedRequestScope.class) != null)) {
              logger.trace(
                  "Not saving EXCLUDED attribute name=[{0}] due to ExcludeFromManagedRequestScope annotation",
                  name);
            } else {
              logger.trace(
                  "Saving non-excluded request attribute name=[{0}] value=[{1}]", name, value);
              savedRequestAttributes.add(new RequestAttribute(name, value));
            }
          }

          if (savedRequestAttributes.size() > 0) {
            setAttribute(BRIDGE_REQ_SCOPE_ATTR_REQUEST_ATTRIBUTES, savedRequestAttributes);
          } else {
            logger.trace("Not saving any non-excluded request attributes");
          }
        }
      } else {
        logger.trace(
            "Not saving any non-excluded request attributes because there are no request attributes!");
      }
    } else {
      logger.trace("Not saving any non-excluded request attributes due to redirect");
    }

    // 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.
    Map<Object, Object> currentFacesContextAttributes = facesContext.getAttributes();
    int mapSize = currentFacesContextAttributes.size();
    List<FacesContextAttribute> savedFacesContextAttributes =
        new ArrayList<FacesContextAttribute>(mapSize);
    Iterator<Map.Entry<Object, Object>> itr = currentFacesContextAttributes.entrySet().iterator();

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

    setAttribute(BRIDGE_REQ_SCOPE_ATTR_FACES_CONTEXT_ATTRIBUTES, savedFacesContextAttributes);
  }

  /**
   * Restores 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 RENDER_PHASE of the portlet lifecycle.
   *
   * @param facesContext The current faces context.
   * @return Flag indicating whether or not a restoration took place.
   */
  @SuppressWarnings("unchecked")
  public boolean restoreScopedData(FacesContext facesContext) {

    if (beganInActionOrEventRequest) {

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

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

        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");
      }

      // 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.
      List<FacesContextAttribute> savedFacesContextAttributes =
          (List<FacesContextAttribute>)
              getAttribute(BRIDGE_REQ_SCOPE_ATTR_FACES_CONTEXT_ATTRIBUTES);

      boolean restoredFacesContextAttibutes = false;

      if (savedFacesContextAttributes != null) {
        Map<Object, Object> currentFacesContextAttributes = facesContext.getAttributes();

        for (FacesContextAttribute facesContextAttribute : savedFacesContextAttributes) {
          Object name = facesContextAttribute.getName();

          // Note: Don't want to restore the BridgeContext because that would be invalid data -- it
          // would
          // contain the ActionRequest/ActionResponse or EventRequest/EventResponse and would
          // overwrite the
          // current RenderRequest/RenderResponse.
          if (!BridgeExt.BRIDGE_CONTEXT_ATTRIBUTE.equals(name)) {
            Object value = facesContextAttribute.getValue();
            logger.trace("Restoring FacesContext attribute name=[{0}] value=[{1}]", name, value);
            currentFacesContextAttributes.put(name, value);
            restoredFacesContextAttibutes = true;
          }
        }
      }

      if (restoredFacesContextAttibutes) {
        logger.debug("Restored FacesContext attributes");
      } else {
        logger.debug("Did not restore any FacesContext attributes");
      }

      return true;
    } else {
      return false;
    }
  }

  /**
   * The overrides for {@link #toString()} and {@link #hashCode()} are necessary because the {@link
   * ConcurrentHashMap} parent class overrides them and causes debug logs to be difficult to
   * interpret.
   */
  @Override
  public String toString() {
    StringBuilder buf = new StringBuilder();
    buf.append(getClass().getName());
    buf.append(BridgeConstants.CHAR_AT);
    buf.append(Integer.toHexString(hashCode()));

    return buf.toString();
  }

  public Object getAttribute(String key) {
    return attributeMap.get(key);
  }

  public void setAttribute(String key, Object value) {
    attributeMap.put(key, value);
  }

  protected boolean isExcludedRequestAttribute(String attributeName, Object value) {
    boolean excluded = false;

    if (!excluded) {

      if (excludedAttributeNames != null) {

        for (String excludedAttribute : excludedAttributeNames) {

          if (attributeName.equals(excludedAttribute)) {
            excluded = true;

            break;
          } else if (excludedAttribute.endsWith(BridgeConstants.CHAR_ASTERISK)) {

            String wildcardNamespace = excludedAttribute;
            int dotPos = wildcardNamespace.lastIndexOf(BridgeConstants.CHAR_PERIOD);

            if (dotPos > 0) {
              wildcardNamespace = wildcardNamespace.substring(0, dotPos);
            }

            if (isNamespaceMatch(attributeName, wildcardNamespace)) {
              excluded = true;

              break;
            }
          }
        }
      }
    }

    if (!excluded) {

      for (String namespace : STANDARD_EXCLUDED_REQUEST_ATTRIBUTE_NAMESPACES) {

        if (isNamespaceMatch(attributeName, namespace)) {
          excluded = true;

          break;
        }
      }
    }

    if (!excluded) {
      excluded = preExistingAttributeNames.contains(attributeName);
    }

    if (!excluded) {

      // EXCLUDED attributes listed in Section 5.1.2 of the JSR 329 Spec
      excluded =
          ((value != null)
              && ((value instanceof ExternalContext)
                  || (value instanceof FacesContext)
                  || (value instanceof HttpSession)
                  || (value instanceof PortalContext)
                  || (value instanceof PortletConfig)
                  || (value instanceof PortletContext)
                  || (value instanceof PortletPreferences)
                  || (value instanceof PortletRequest)
                  || (value instanceof PortletResponse)
                  || (value instanceof PortletSession)
                  || (value instanceof ServletConfig)
                  || (value instanceof ServletContext)
                  || (value instanceof ServletRequest)
                  || (value instanceof ServletResponse)));
    }

    return excluded;
  }

  public Flash getFlash() {
    return flash;
  }

  public void setFlash(Flash flash) {
    this.flash = flash;
  }

  protected boolean isNamespaceMatch(String attributeName, String namespace) {

    boolean match = false;

    String attributeNamespace = attributeName;
    int dotPos = attributeNamespace.lastIndexOf(BridgeConstants.CHAR_PERIOD);

    if (dotPos > 0) {
      attributeNamespace = attributeNamespace.substring(0, dotPos);
    }

    if (namespace.equals(attributeNamespace)) {
      match = true;
    }

    return match;
  }

  public String getId() {
    return idPrefix + idSuffix;
  }

  public void setIdPrefix(String idPrefix) {
    this.idPrefix = idPrefix;
  }

  public Map<String, Object> getManagedBeanMap() {

    if (managedBeanMap == null) {
      managedBeanMap = new HashMap<String, Object>();
    }

    return managedBeanMap;
  }

  public PortletMode getPortletMode() {
    return portletMode;
  }

  public void setPortletMode(PortletMode portletMode) {
    this.portletMode = portletMode;
  }

  public void setPortletModeChanged(boolean portletModeChanged) {
    this.portletModeChanged = portletModeChanged;
  }

  /**
   * According to section 5.1.2 of the JSR 329 spec, the request attributes that exist before the
   * bridge acquires the FacesContext must not be part of the bridge request scope. Having noted
   * that, we have to save-off a list of names of these pre-existing request attributes, so that we
   * know to NOT restore them.
   */
  protected Set<String> getPreExistingRequestAttributeNames(PortletRequest portletRequest) {
    Set<String> attributeNames = null;
    Enumeration<String> requestAttributeNames = portletRequest.getAttributeNames();

    if (requestAttributeNames != null) {
      attributeNames = new HashSet<String>();

      while (requestAttributeNames.hasMoreElements()) {
        String attributeName = requestAttributeNames.nextElement();
        attributeNames.add(attributeName);
        logger.trace("Saving name of pre-existing request attribute [{0}]", attributeName);
      }
    }

    return attributeNames;
  }

  @SuppressWarnings("unchecked")
  public Map<String, String> getPreservedActionParameterMap() {
    return (Map<String, String>) getAttribute(BRIDGE_REQ_SCOPE_ATTR_ACTION_PARAMS);
  }

  public String getPreservedViewStateParam() {
    return (String) getAttribute(ResponseStateManager.VIEW_STATE_PARAM);
  }

  public void setRedirect(boolean redirect) {
    this.redirect = redirect;
  }
}
/** @author Neil Griffin */
public class RequestParameterMapMultiPartImpl extends RequestParameterMap {

  // Logger
  private static final Logger logger =
      LoggerFactory.getLogger(RequestParameterMapMultiPartImpl.class);

  // Private Constants
  private static final String CONTEXT_PARAM_UPLOADED_FILES_DIR = "javax.faces.UPLOADED_FILES_DIR";
  private static final String CONTEXT_PARAM_UPLOADED_FILE_MAX_SIZE =
      "javax.faces.UPLOADED_FILE_MAX_SIZE";
  private static final String JAVA_IO_TMPDIR = "java.io.tmpdir";
  private static final int DEFAULT_FILE_MAX_SIZE = 104857600; // 100MB

  // Private Data Members
  private Map<String, String> requestParameterMap;
  private Map<String, List<UploadedFile>> requestParameterFileMap;

  @SuppressWarnings("unchecked")
  public RequestParameterMapMultiPartImpl(
      BridgeContext bridgeContext, ClientDataRequest clientDataRequest) {

    try {

      PortletSession portletSession = clientDataRequest.getPortletSession();
      PortletContext portletContext = portletSession.getPortletContext();

      // Determine the uploaded files directory path according to the JSF 2.2 proposal:
      // https://javaserverfaces-spec-public.dev.java.net/issues/show_bug.cgi?id=690
      String uploadedFilesDir = portletContext.getInitParameter(CONTEXT_PARAM_UPLOADED_FILES_DIR);

      if (uploadedFilesDir == null) {
        uploadedFilesDir = System.getProperty(JAVA_IO_TMPDIR);

        if (logger.isDebugEnabled()) {
          logger.debug(
              "The web.xml context-param name=[{0}] not found, using default system property=[{1}] value=[{2}]",
              new Object[] {CONTEXT_PARAM_UPLOADED_FILES_DIR, JAVA_IO_TMPDIR, uploadedFilesDir});
        }
      } else {

        if (logger.isDebugEnabled()) {
          logger.debug(
              "Using web.xml context-param name=[{0}] value=[{1}]",
              new Object[] {CONTEXT_PARAM_UPLOADED_FILES_DIR, uploadedFilesDir});
        }
      }

      // Using the portlet sessionId, determine a unique folder path and create the path if it does
      // not exist.
      String sessionId = portletSession.getId();
      File uploadedFilesPath = new File(uploadedFilesDir, sessionId);

      if (!uploadedFilesPath.exists()) {

        try {
          uploadedFilesPath.mkdirs();
        } catch (SecurityException e) {
          uploadedFilesDir = System.getProperty(JAVA_IO_TMPDIR);
          logger.error(
              "Security exception message=[{0}] when trying to create unique path=[{1}] so using default system property=[{2}] value=[{3}]",
              new Object[] {
                e.getMessage(), uploadedFilesPath.toString(), JAVA_IO_TMPDIR, uploadedFilesDir
              });
          uploadedFilesPath = new File(uploadedFilesDir, sessionId);
          uploadedFilesPath.mkdirs();
        }
      }

      // Initialize commons-fileupload with the file upload path.
      DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
      diskFileItemFactory.setRepository(uploadedFilesPath);

      // Initialize commons-fileupload so that uploaded temporary files are not automatically
      // deleted.
      diskFileItemFactory.setFileCleaningTracker(null);

      // Initialize the commons-fileupload size threshold to zero, so that all files will be dumped
      // to disk
      // instead of staying in memory.
      diskFileItemFactory.setSizeThreshold(0);

      // Determine the max file upload size threshold in bytes.
      String uploadedFilesMaxSize =
          portletContext.getInitParameter(CONTEXT_PARAM_UPLOADED_FILE_MAX_SIZE);
      int fileMaxSize = DEFAULT_FILE_MAX_SIZE;

      if (uploadedFilesMaxSize == null) {

        if (logger.isDebugEnabled()) {
          logger.debug(
              "The web.xml context-param name=[{0}] not found, using default=[{1}] bytes",
              new Object[] {CONTEXT_PARAM_UPLOADED_FILE_MAX_SIZE, DEFAULT_FILE_MAX_SIZE});
        }
      } else {

        try {
          fileMaxSize = Integer.parseInt(uploadedFilesMaxSize);

          if (logger.isDebugEnabled()) {
            logger.debug(
                "Using web.xml context-param name=[{0}] value=[{1}] bytes",
                new Object[] {CONTEXT_PARAM_UPLOADED_FILE_MAX_SIZE, fileMaxSize});
          }
        } catch (NumberFormatException e) {
          logger.error(
              "Invalid value=[{0}] for web.xml context-param name=[{1}] using default=[{2}] bytes.",
              new Object[] {
                uploadedFilesMaxSize, CONTEXT_PARAM_UPLOADED_FILE_MAX_SIZE, DEFAULT_FILE_MAX_SIZE
              });
        }
      }

      // Parse the request parameters and save all uploaded files in a map.
      PortletFileUpload portletFileUpload = new PortletFileUpload(diskFileItemFactory);
      portletFileUpload.setFileSizeMax(fileMaxSize);
      requestParameterMap = new HashMap<String, String>();
      requestParameterFileMap = new HashMap<String, List<UploadedFile>>();

      // Get the namespace that might be found in request parameter names.
      String namespace = bridgeContext.getPortletContainer().getResponseNamespace();

      // FACES-271: Include name+value pairs found in the ActionRequest.
      PortletContainer portletContainer = bridgeContext.getPortletContainer();
      Set<Map.Entry<String, String[]>> actionRequestParameterSet =
          clientDataRequest.getParameterMap().entrySet();

      for (Map.Entry<String, String[]> mapEntry : actionRequestParameterSet) {

        String parameterName = mapEntry.getKey();
        int pos = parameterName.indexOf(namespace);

        if (pos >= 0) {
          parameterName = parameterName.substring(pos + namespace.length());
        }

        String[] parameterValues = mapEntry.getValue();

        if (parameterValues.length > 0) {
          String fixedRequestParameterValue =
              portletContainer.fixRequestParameterValue(parameterValues[0]);
          requestParameterMap.put(parameterName, fixedRequestParameterValue);
          logger.debug(
              "Found in ActionRequest: {0}=[{1}]", parameterName, fixedRequestParameterValue);
        }
      }

      UploadedFileFactory uploadedFileFactory =
          (UploadedFileFactory) BridgeFactoryFinder.getFactory(UploadedFileFactory.class);

      // Begin parsing the request for file parts:
      try {
        FileItemIterator fileItemIterator = null;

        if (clientDataRequest instanceof ResourceRequest) {
          ResourceRequest resourceRequest = (ResourceRequest) clientDataRequest;
          fileItemIterator =
              portletFileUpload.getItemIterator(new ActionRequestAdapter(resourceRequest));
        } else {
          ActionRequest actionRequest = (ActionRequest) clientDataRequest;
          fileItemIterator = portletFileUpload.getItemIterator(actionRequest);
        }

        boolean optimizeNamespace =
            BooleanHelper.toBoolean(
                bridgeContext.getInitParameter(
                    BridgeConfigConstants.PARAM_OPTIMIZE_PORTLET_NAMESPACE1),
                true);

        if (fileItemIterator != null) {

          int totalFiles = 0;

          // For each field found in the request:
          while (fileItemIterator.hasNext()) {

            try {
              totalFiles++;

              // Get the stream of field data from the request.
              FileItemStream fieldStream = (FileItemStream) fileItemIterator.next();

              // Get field name from the field stream.
              String fieldName = fieldStream.getFieldName();

              // If namespace optimization is enabled and the namespace is present in the field
              // name,
              // then remove the portlet namespace from the field name.
              if (optimizeNamespace) {
                int pos = fieldName.indexOf(namespace);

                if (pos >= 0) {
                  fieldName = fieldName.substring(pos + namespace.length());
                }
              }

              // Get the content-type, and file-name from the field stream.
              String contentType = fieldStream.getContentType();
              boolean formField = fieldStream.isFormField();

              String fileName = null;

              try {
                fileName = fieldStream.getName();
              } catch (InvalidFileNameException e) {
                fileName = e.getName();
              }

              // Copy the stream of file data to a temporary file. NOTE: This is necessary even if
              // the
              // current field is a simple form-field because the call below to
              // diskFileItem.getString()
              // will fail otherwise.
              DiskFileItem diskFileItem =
                  (DiskFileItem)
                      diskFileItemFactory.createItem(fieldName, contentType, formField, fileName);
              Streams.copy(fieldStream.openStream(), diskFileItem.getOutputStream(), true);

              // If the current field is a simple form-field, then save the form field value in the
              // map.
              if (diskFileItem.isFormField()) {
                String requestParameterValue =
                    diskFileItem.getString(clientDataRequest.getCharacterEncoding());
                String fixedRequestParameterValue =
                    portletContainer.fixRequestParameterValue(requestParameterValue);
                requestParameterMap.put(fieldName, fixedRequestParameterValue);
                logger.debug("{0}=[{1}]", fieldName, fixedRequestParameterValue);
              } else {

                File tempFile = diskFileItem.getStoreLocation();

                // If the copy was successful, then
                if (tempFile.exists()) {

                  // Copy the commons-fileupload temporary file to a file in the same temporary
                  // location, but with the filename provided by the user in the upload. This has
                  // two
                  // benefits: 1) The temporary file will have a nice meaningful name. 2) By copying
                  // the file, the developer can have access to a semi-permanent file, because the
                  // commmons-fileupload DiskFileItem.finalize() method automatically deletes the
                  // temporary one.
                  String tempFileName = tempFile.getName();
                  String tempFileAbsolutePath = tempFile.getAbsolutePath();

                  String copiedFileName = stripIllegalCharacters(fileName);

                  String copiedFileAbsolutePath =
                      tempFileAbsolutePath.replace(tempFileName, copiedFileName);
                  File copiedFile = new File(copiedFileAbsolutePath);
                  FileUtils.copyFile(tempFile, copiedFile);

                  // If present, build up a map of headers.
                  Map<String, List<String>> headersMap = new HashMap<String, List<String>>();
                  FileItemHeaders fileItemHeaders = fieldStream.getHeaders();

                  if (fileItemHeaders != null) {
                    Iterator<String> headerNameItr = fileItemHeaders.getHeaderNames();

                    if (headerNameItr != null) {

                      while (headerNameItr.hasNext()) {
                        String headerName = headerNameItr.next();
                        Iterator<String> headerValuesItr = fileItemHeaders.getHeaders(headerName);
                        List<String> headerValues = new ArrayList<String>();

                        if (headerValuesItr != null) {

                          while (headerValuesItr.hasNext()) {
                            String headerValue = headerValuesItr.next();
                            headerValues.add(headerValue);
                          }
                        }

                        headersMap.put(headerName, headerValues);
                      }
                    }
                  }

                  // Put a valid UploadedFile instance into the map that contains all of the
                  // uploaded file's attributes, along with a successful status.
                  Map<String, Object> attributeMap = new HashMap<String, Object>();
                  String id = Long.toString(((long) hashCode()) + System.currentTimeMillis());
                  String message = null;
                  UploadedFile uploadedFile =
                      uploadedFileFactory.getUploadedFile(
                          copiedFileAbsolutePath,
                          attributeMap,
                          diskFileItem.getCharSet(),
                          diskFileItem.getContentType(),
                          headersMap,
                          id,
                          message,
                          fileName,
                          diskFileItem.getSize(),
                          UploadedFile.Status.FILE_SAVED);

                  requestParameterMap.put(fieldName, copiedFileAbsolutePath);
                  addUploadedFile(fieldName, uploadedFile);
                  logger.debug(
                      "Received uploaded file fieldName=[{0}] fileName=[{1}]", fieldName, fileName);
                }
              }
            } catch (Exception e) {
              logger.error(e);

              UploadedFile uploadedFile = uploadedFileFactory.getUploadedFile(e);
              String fieldName = Integer.toString(totalFiles);
              addUploadedFile(fieldName, uploadedFile);
            }
          }
        }
      }

      // If there was an error in parsing the request for file parts, then put a bogus UploadedFile
      // instance in
      // the map so that the developer can have some idea that something went wrong.
      catch (Exception e) {
        logger.error(e);

        UploadedFile uploadedFile = uploadedFileFactory.getUploadedFile(e);
        addUploadedFile("unknown", uploadedFile);
      }

      clientDataRequest.setAttribute(PARAM_UPLOADED_FILES, requestParameterFileMap);

      // If not found in the request, Section 6.9 of the Bridge spec requires that the value of the
      // ResponseStateManager.RENDER_KIT_ID_PARAM request parameter be set to the value of the
      // "javax.portlet.faces.<portletName>.defaultRenderKitId" PortletContext attribute.
      String renderKitIdParam = requestParameterMap.get(ResponseStateManager.RENDER_KIT_ID_PARAM);

      if (renderKitIdParam == null) {
        renderKitIdParam = bridgeContext.getDefaultRenderKitId();

        if (renderKitIdParam != null) {
          requestParameterMap.put(ResponseStateManager.RENDER_KIT_ID_PARAM, renderKitIdParam);
        }
      }
    } catch (Exception e) {
      logger.error(e.getMessage(), e);
    }
  }

  protected void addUploadedFile(String fieldName, UploadedFile uploadedFile) {
    List<UploadedFile> uploadedFiles = requestParameterFileMap.get(fieldName);

    if (uploadedFiles == null) {
      uploadedFiles = new ArrayList<UploadedFile>();
      requestParameterFileMap.put(fieldName, uploadedFiles);
    }

    uploadedFiles.add(uploadedFile);
  }

  @Override
  protected AbstractPropertyMapEntry<String> createPropertyMapEntry(String name) {
    return new RequestParameterMapEntryMultiPart(name, requestParameterMap);
  }

  @Override
  protected void removeProperty(String name) {
    throw new UnsupportedOperationException();
  }

  protected String stripIllegalCharacters(String fileName) {

    // FACES-64: Need to strip out invalid characters.
    // http://technet.microsoft.com/en-us/library/cc956689.aspx
    String strippedFileName = fileName;

    if (fileName != null) {
      strippedFileName = fileName.replaceAll("[\\\\/\\[\\]:|<>+;=.?\"]", "-");
    }

    return strippedFileName;
  }

  @Override
  protected String getProperty(String name) {
    return requestParameterMap.get(name);
  }

  @Override
  protected void setProperty(String name, String value) {
    throw new UnsupportedOperationException();
  }

  @Override
  protected Enumeration<String> getPropertyNames() {

    // Note#1: Section 6.9 of the Bridge spec requires that a parameter name be added to the return
    // value of
    // ExternalContext.getRequestParameterNames() for ResponseStateManager.RENDER_KIT_ID_PARAM. This
    // will
    // automatically be the case because this class builds up its own internal requestParameterMap
    // in the
    // constructor that will contain the ResponseStateManager.RENDER_KIT_ID_PARAM if required.

    // Note#2: This can't be cached because the caller basically wants a new enumeration to iterate
    // over each time.
    return Collections.enumeration(requestParameterMap.keySet());
  }

  /**
   * Since {@link PortletFileUpload#parseRequest(ActionRequest)} only works with {@link
   * ActionRequest}, this adapter class is necessary to force commons-fileupload to work with
   * ResourceRequest (Ajax file upload).
   *
   * @author Neil Griffin
   */
  protected class ActionRequestAdapter implements ActionRequest {

    private ResourceRequest resourceRequest;

    public ActionRequestAdapter(ResourceRequest resourceRequest) {
      this.resourceRequest = resourceRequest;
    }

    public void removeAttribute(String name) {
      resourceRequest.removeAttribute(name);
    }

    public Object getAttribute(String name) {
      return resourceRequest.getAttribute(name);
    }

    public void setAttribute(String name, Object value) {
      resourceRequest.setAttribute(name, value);
    }

    public Enumeration<String> getAttributeNames() {
      return resourceRequest.getAttributeNames();
    }

    public String getAuthType() {
      return resourceRequest.getAuthType();
    }

    public String getCharacterEncoding() {
      return resourceRequest.getCharacterEncoding();
    }

    public void setCharacterEncoding(String enc) throws UnsupportedEncodingException {
      resourceRequest.setCharacterEncoding(enc);
    }

    public int getContentLength() {
      return resourceRequest.getContentLength();
    }

    public String getContentType() {
      return resourceRequest.getContentType();
    }

    public String getContextPath() {
      return resourceRequest.getContextPath();
    }

    public Cookie[] getCookies() {
      return resourceRequest.getCookies();
    }

    public boolean isPortletModeAllowed(PortletMode mode) {
      return resourceRequest.isPortletModeAllowed(mode);
    }

    public boolean isRequestedSessionIdValid() {
      return resourceRequest.isRequestedSessionIdValid();
    }

    public boolean isWindowStateAllowed(WindowState state) {
      return resourceRequest.isWindowStateAllowed(state);
    }

    public boolean isSecure() {
      return resourceRequest.isSecure();
    }

    public boolean isUserInRole(String role) {
      return resourceRequest.isUserInRole(role);
    }

    public Locale getLocale() {
      return resourceRequest.getLocale();
    }

    public Enumeration<Locale> getLocales() {
      return resourceRequest.getLocales();
    }

    public String getMethod() {
      return resourceRequest.getMethod();
    }

    public String getParameter(String name) {
      return resourceRequest.getParameter(name);
    }

    public Map<String, String[]> getParameterMap() {
      return resourceRequest.getParameterMap();
    }

    public Enumeration<String> getParameterNames() {
      return resourceRequest.getParameterNames();
    }

    public String[] getParameterValues(String name) {
      return resourceRequest.getParameterValues(name);
    }

    public PortalContext getPortalContext() {
      return resourceRequest.getPortalContext();
    }

    public InputStream getPortletInputStream() throws IOException {
      return resourceRequest.getPortletInputStream();
    }

    public PortletMode getPortletMode() {
      return resourceRequest.getPortletMode();
    }

    public PortletSession getPortletSession() {
      return resourceRequest.getPortletSession();
    }

    public PortletSession getPortletSession(boolean create) {
      return resourceRequest.getPortletSession();
    }

    public PortletPreferences getPreferences() {
      return resourceRequest.getPreferences();
    }

    public Map<String, String[]> getPrivateParameterMap() {
      return resourceRequest.getPrivateParameterMap();
    }

    public Enumeration<String> getProperties(String name) {
      return resourceRequest.getProperties(name);
    }

    public String getProperty(String name) {
      return resourceRequest.getProperty(name);
    }

    public Enumeration<String> getPropertyNames() {
      return resourceRequest.getPropertyNames();
    }

    public Map<String, String[]> getPublicParameterMap() {
      return resourceRequest.getPublicParameterMap();
    }

    public BufferedReader getReader() throws UnsupportedEncodingException, IOException {
      return resourceRequest.getReader();
    }

    public String getRemoteUser() {
      return resourceRequest.getRemoteUser();
    }

    public String getRequestedSessionId() {
      return resourceRequest.getRequestedSessionId();
    }

    public String getResponseContentType() {
      return resourceRequest.getResponseContentType();
    }

    public Enumeration<String> getResponseContentTypes() {
      return resourceRequest.getResponseContentTypes();
    }

    public String getScheme() {
      return resourceRequest.getScheme();
    }

    public String getServerName() {
      return resourceRequest.getServerName();
    }

    public int getServerPort() {
      return resourceRequest.getServerPort();
    }

    public Principal getUserPrincipal() {
      return resourceRequest.getUserPrincipal();
    }

    public String getWindowID() {
      return resourceRequest.getWindowID();
    }

    public WindowState getWindowState() {
      return resourceRequest.getWindowState();
    }
  }
}
/** @author Neil Griffin */
public class DocumentDataModel extends LazyDataModel<DocLibFileEntry> implements Serializable {

  // serialVersionUID
  private static final long serialVersionUID = 4895165386116316346L;

  // Logger
  private static final Logger logger = LoggerFactory.getLogger(DocumentDataModel.class);

  // Private Data Members
  private DLFolder dlFolder;
  private String portalURL;
  private String pathContext;
  private PermissionChecker permissionChecker;

  public DocumentDataModel(
      DLFolder dlFolder,
      int rowsPerPage,
      String portalURL,
      String pathContext,
      PermissionChecker permissionChecker) {

    this.dlFolder = dlFolder;
    setRowsPerPage(rowsPerPage);
    this.portalURL = portalURL;
    this.pathContext = pathContext;
    this.permissionChecker = permissionChecker;
    setSortColumn("title");
    setSortAscending(true);
  }

  @Override
  public int countRows() {
    return findViewableDocuments().size();
  }

  @Override
  public void deleteRow(Object primaryKey) throws IOException {
    long fileEntryId = (Long) primaryKey;

    try {
      DLFileEntry dlFileEntry = DLFileEntryLocalServiceUtil.getFileEntry(fileEntryId);
      DLFileEntryServiceUtil.deleteFileEntry(
          dlFolder.getGroupId(), dlFolder.getFolderId(), dlFileEntry.getName());
    } catch (Exception e) {
      logger.error(e.getMessage(), e);
      throw new IOException(e.getMessage());
    }
  }

  @Override
  public List<DocLibFileEntry> findRows(int startRow, int finishRow) {

    List<DocLibFileEntry> viewableDocuments = findViewableDocuments();
    int totalViewableDocuments = viewableDocuments.size();

    if ((totalViewableDocuments > 0)
        && (startRow != QueryUtil.ALL_POS)
        && (finishRow != QueryUtil.ALL_POS)) {

      if (startRow > totalViewableDocuments) {
        startRow = totalViewableDocuments;
      }

      if (finishRow > totalViewableDocuments) {
        finishRow = totalViewableDocuments;
      }

      int includeFinishRowToo = finishRow + 1;
      viewableDocuments = viewableDocuments.subList(startRow, includeFinishRowToo);
    }

    return viewableDocuments;
  }

  protected List<DocLibFileEntry> findViewableDocuments() {
    List<DocLibFileEntry> viewableDocuments = new ArrayList<DocLibFileEntry>();

    try {
      long folderGroupId = dlFolder.getGroupId();
      long folderId = dlFolder.getFolderId();

      List<DLFileEntry> dlFileEntries = null;

      if (DLFolderPermission.contains(
          permissionChecker, folderGroupId, folderId, ActionKeys.VIEW)) {
        OrderByComparator orderByComparator =
            DocumentComparatorFactory.getComparator(getSortColumn(), isSortAscending());

        dlFileEntries =
            DLFileEntryServiceUtil.getFileEntries(
                folderGroupId, folderId, QueryUtil.ALL_POS, QueryUtil.ALL_POS, orderByComparator);
      }

      if (dlFileEntries != null) {

        for (DLFileEntry dlFileEntry : dlFileEntries) {
          boolean permittedToViewDocument = false;

          try {
            permittedToViewDocument =
                DLFileEntryPermission.contains(permissionChecker, dlFileEntry, ActionKeys.VIEW);
          } catch (Exception e) {
            logger.error(e.getMessage(), e);
          }

          viewableDocuments.add(
              new DocLibFileEntry(
                  dlFileEntry, portalURL, pathContext, folderGroupId, permittedToViewDocument));
        }
      }
    } catch (Exception e) {
      logger.error(e.getMessage(), e);
    }

    return viewableDocuments;
  }

  @Override
  public Object getPrimaryKey(DocLibFileEntry docLibFileEntry) {
    return docLibFileEntry.getFileEntryId();
  }
}
/**
 * This class is a portlet-specific implementation of the Faces Application. Its purpose is to
 * override the {@link createComponent(String)} and {@link getResourceHandler()} methods so that
 * additional portlet-specific instances can be introduced into the Faces lifecycle. Note that
 * instances of this class are generated by the custom {@link ApplicationFactoryImpl}.
 *
 * @author Neil Griffin
 */
public class ApplicationImpl extends ApplicationWrapper {

  // Logger
  private static final Logger logger = LoggerFactory.getLogger(ApplicationImpl.class);

  // Private Data Members
  private Application wrappedApplication;
  private boolean wrapHandlerAtRuntime = true;

  public ApplicationImpl(Application application) {
    this.wrappedApplication = application;
  }

  /**
   * This method provides the ability to supply an instance of the bridge API's {@link
   * PortletNamingContainerUIViewRoot} class which properly handles namespacing of "id" attributes
   * for portlets.
   *
   * @see Application#createComponent(String)
   */
  @Override
  public UIComponent createComponent(String componentType) throws FacesException {

    if (componentType.equals(UIViewRoot.COMPONENT_TYPE) && BridgeUtil.isPortletRequest()) {
      return new UIViewRootBridgeImpl();
    } else {
      return wrappedApplication.createComponent(componentType);
    }
  }

  @Override
  public UIComponent createComponent(
      FacesContext facesContext, String componentType, String rendererType) {

    UIComponent wrappedUIComponent =
        wrappedApplication.createComponent(facesContext, componentType, rendererType);

    if (componentType.equals(DataPaginator.COMPONENT_TYPE)) {

      // Workaround for: http://jira.icesoft.org/browse/ICE-6398
      DataPaginator dataPaginator = new DataPaginatorBridgeImpl(wrappedUIComponent);

      try {
        dataPaginator.setUIData(dataPaginator.findUIData(facesContext));
        wrappedUIComponent = dataPaginator;
      } catch (Exception e) {
        logger.error(e);
      }
    }

    return wrappedUIComponent;
  }

  /**
   * The normal way of adding a {@link NavigationHandler} to a JSF application is to have a
   * navigation-handler element in the faces-config.xml descriptor. Unfortunately the bridge can't
   * use this mechanism, because it must ensure that the {@link BridgeNavigationHandler} is the
   * outermost instance in the chain-of-responsibility. While this could be done with
   * &lt;ordering&gt;&lt;after&gt;&lt;others/&gt;&lt;/after&gt;&lt;/others&gt; in the bridge's
   * META-INF/faces-config.xml file, the bridge must use
   * &lt;ordering&gt;&lt;before&gt;&lt;others/&gt;&lt;/before&gt;&lt;/others&gt; in order to
   * maintain compatibility with ICEfaces and other component libraries. So because of this, it is
   * necessary to provide this override of the {@link #getNavigationHandler()} method in order to
   * ensure that the {@link BridgeNavigationHandler} is the outermost instance.
   */
  @Override
  public NavigationHandler getNavigationHandler() {

    // NOTE: Mojarra uses a servlet context listener to pre-load all the faces-config.xml files in
    // the classpath.
    // During this initialization, it will call this method override for whatever reason. But we
    // can't ask the
    // BridgeFactoryFinder to find the BridgeNavigationHandler factory at that time because it
    // relies on the
    // PortletContext object which can only be retrieved at runtime. So for this reason, we have to
    // delay the
    // wrapping the Faces default NavigationHandler until a PortletRequest happens at runtime.
    if (wrapHandlerAtRuntime) {

      FacesContext facesContext = FacesContext.getCurrentInstance();

      if (facesContext != null) {

        try {

          PortletRequest portletRequest =
              (PortletRequest) facesContext.getExternalContext().getRequest();

          if (portletRequest != null) {
            BridgeNavigationHandler bridgeNavigationHandler =
                new BridgeNavigationHandlerImpl(super.getNavigationHandler());
            super.setNavigationHandler(bridgeNavigationHandler);
            wrapHandlerAtRuntime = false;
          }
        } catch (UnsupportedOperationException e) {
          // ignore -- MyFaces does not permit calling getRequest() during startup.
        }
      }
    }

    return super.getNavigationHandler();
  }

  /** @see ApplicationWrapper#getWrapped() */
  @Override
  public Application getWrapped() {
    return wrappedApplication;
  }
}
/** @author Neil Griffin */
public class RequestHeaderValuesMap extends CaseInsensitiveHashMap<String[]> {

  // Private Constants
  private static final String CHARSET = "charset";
  private static final String PARTIAL_AJAX = "partial/ajax";

  // serialVersionUID
  private static final long serialVersionUID = 4910578014366086738L;

  // Logger
  private static final Logger logger = LoggerFactory.getLogger(RequestHeaderValuesMap.class);

  // Private Constants
  private static final String HEADER_ACCEPT = "Accept";
  private static final String HEADER_ACCEPT_LANGUAGE = "Accept-Language";
  private static final String HEADER_CONTENT_TYPE = "Content-Type";
  private static final String HEADER_FACES_REQUEST = "Faces-Request";

  public RequestHeaderValuesMap(
      PortletRequest portletRequest, Map<String, String> requestParameterMap) {
    Enumeration<String> propertyNames = portletRequest.getPropertyNames();
    boolean foundAccept = false;
    boolean foundContentType = false;
    boolean foundFacesRequest = false;

    if (propertyNames != null) {

      while (propertyNames.hasMoreElements()) {

        boolean addHeader = true;
        String name = propertyNames.nextElement();

        if (name.equalsIgnoreCase(HEADER_ACCEPT_LANGUAGE)) {
          Enumeration<Locale> locales = portletRequest.getLocales();

          if (locales != null) {
            addHeader = false;

            StringBuilder buf = new StringBuilder();

            for (int i = 0; locales.hasMoreElements(); i++) {

              if (i > 0) {
                buf.append(BridgeConstants.CHAR_COMMA);
              }

              Locale locale = locales.nextElement();
              buf.append(locale.getLanguage());

              String country = locale.getCountry();
              if ((country != null) && (country.length() > 0)) {
                buf.append(BridgeConstants.CHAR_DASH);
                buf.append(country);
              }
            }

            super.put(name, new String[] {buf.toString()});
          }
        }

        if (addHeader) {
          Enumeration<String> properties = portletRequest.getProperties(name);

          StringBuilder buf = new StringBuilder();

          if (properties != null) {

            for (int i = 0; properties.hasMoreElements(); i++) {

              if (i > 0) {
                buf.append(BridgeConstants.CHAR_COMMA);
              }

              buf.append(properties.nextElement());
            }
          }

          String values = buf.toString();
          super.put(name, new String[] {values});

          // NOTE: Need to check that the portlet container actually provided a value before the
          // bridge can
          // claim that it has detected "Accept", "Content-Type", or "Faces-Request".
          // http://issues.liferay.com/browse/FACES-34
          if ((values != null) && (values.length() > 0)) {

            if (!foundAccept) {
              foundAccept = name.equalsIgnoreCase(HEADER_ACCEPT);
            }

            if (!foundContentType) {
              foundContentType = name.equalsIgnoreCase(HEADER_CONTENT_TYPE);
            }

            if (!foundFacesRequest) {
              foundFacesRequest = name.equalsIgnoreCase(HEADER_FACES_REQUEST);
            }
          }
        }
      }
    }

    if (!foundAccept) {
      addAcceptHeader(portletRequest);
    }

    if (!foundContentType) {
      addContentTypeHeader(portletRequest);
    }

    if (!foundFacesRequest) {

      // If this is a ResourceRequest, and the resource handler chain doesn't consider this to be a
      // resource, then
      // we assume that it's Ajax and add the "Faces-Request" header with value "partial/ajax". Note
      // that this is
      // normally done by the jsf.js JavaScript library, but in a portlet environment, the original
      // XmlHttpRequest
      // is not made available to the portlet bridge.
      if (portletRequest instanceof ResourceRequest) {

        // If the BridgeExt.FACES_AJAX_PARAMETER request parameter is "true" then set the
        // "partial/ajax" header
        // so that the Ajax-based PartialResponseWriter renders XML. Otherwise, since the URL was
        // probably setup
        // with "portlet:resource" don't setup the header because it needs to fully run the JSF
        // lifecycle with a
        // real (non-parital) ResponseWriter that renders HTML.
        String facesAjaxParam = requestParameterMap.get(BridgeExt.FACES_AJAX_PARAMETER);

        if ((facesAjaxParam != null) && BooleanHelper.isTrueToken(facesAjaxParam)) {
          put(HEADER_FACES_REQUEST, new String[] {PARTIAL_AJAX});
        }
      }
    }
  }

  /**
   * Adds an "Accept" header to the hashmap, according to the response content types in the
   * specified request. Example Value: Accept: text/html
   *
   * @param portletRequest The current portlet request.
   * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
   */
  protected void addAcceptHeader(PortletRequest portletRequest) {
    StringBuilder header = new StringBuilder();
    header.append(HEADER_ACCEPT);
    header.append(": ");

    Enumeration<String> responseContentTypes = portletRequest.getResponseContentTypes();
    boolean firstElement = true;

    while (responseContentTypes.hasMoreElements()) {

      if (!firstElement) {
        header.append(",");
      }

      String responseContentType = responseContentTypes.nextElement();
      header.append(responseContentType);
      firstElement = false;
    }

    String acceptHeader = header.toString();
    logger.debug("Adding acceptHeader=[{0}] to header map", acceptHeader);
    put(HEADER_ACCEPT, new String[] {header.toString()});
  }

  /**
   * Adds a "Content-Type" header to the hashmap, according to the content-type and
   * character-encoding in the specified request. Example Value: Content-Type: text/html;
   * charset=ISO-8859-4
   *
   * @param portletRequest The current portlet request.
   * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17
   */
  protected void addContentTypeHeader(PortletRequest portletRequest) {

    // If the specified portletRequest contains characterSetEncoding and contentType information,
    // then
    // use that to build the header.
    if (portletRequest instanceof ClientDataRequest) {
      ClientDataRequest clientDataRequest = (ClientDataRequest) portletRequest;
      String contentType = clientDataRequest.getContentType();
      String characterSetEncoding = clientDataRequest.getCharacterEncoding();

      StringBuilder header = new StringBuilder();
      header.append(HEADER_CONTENT_TYPE);
      header.append(BridgeConstants.CHAR_COLON);
      header.append(BridgeConstants.CHAR_SPACE);
      header.append(contentType);

      if (characterSetEncoding != null) {
        header.append(BridgeConstants.CHAR_SEMICOLON);
        header.append(BridgeConstants.CHAR_SPACE);
        header.append(CHARSET);
        header.append(BridgeConstants.CHAR_EQUALS);
        header.append(characterSetEncoding);
      }

      String contentTypeHeader = header.toString();
      logger.debug("Adding contentTypeHeader=[{0}] to header map", contentTypeHeader);
      put(HEADER_CONTENT_TYPE, new String[] {header.toString()});
    } else {
      // TCK TestPage142: getRequestHeaderMapRenderTest
    }
  }

  protected void addFacesRequestPartialAjaxHeader(PortletRequest portletRequest) {
    // TODO: This method was stubbed-out but was never implemented?
  }
}