docs: @Override
      public IPortalRequestInfo getPortalRequestInfo(HttpServletRequest request) {
          request = this.portalRequestUtils.getOriginalPortalRequest(request);
          final IPortalRequestInfo cachedPortalRequestInfo =
                  (IPortalRequestInfo) request.getAttribute(PORTAL_REQUEST_INFO_ATTR);
          if (cachedPortalRequestInfo != null) {
              if (logger.isDebugEnabled()) {
                  logger.debug("short-circuit: found portalRequestInfo within request attributes");
              }
              return cachedPortalRequestInfo;
          }
  
          synchronized (PortalWebUtils.getRequestAttributeMutex(request)) {
              // set a flag to say this request is currently being parsed
              final Boolean inProgressAttr =
                      (Boolean) request.getAttribute(PORTAL_REQUEST_PARSING_IN_PROGRESS_ATTR);
              if (inProgressAttr != null && inProgressAttr) {
                  if (logger.isDebugEnabled()) {
                      logger.warn("Portal request info parsing already in progress, returning null");
                  }
                  return null;
              }
              request.setAttribute(PORTAL_REQUEST_PARSING_IN_PROGRESS_ATTR, Boolean.TRUE);
          }
  
          try {
              // Clone the parameter map so data can be removed from it as it is parsed to help
              // determine
              // what to do with non-namespaced parameters
              final Map<String, String[]> parameterMap = new ParameterMap(request.getParameterMap());
  
              final String requestPath = this.urlPathHelper.getPathWithinApplication(request);
  
              if (LEGACY_URL_PATHS.contains(requestPath)) {
                  return parseLegacyPortalUrl(request, parameterMap);
              }
  
              final IUrlNodeSyntaxHelper urlNodeSyntaxHelper =
                      this.urlNodeSyntaxHelperRegistry.getCurrentUrlNodeSyntaxHelper(request);
  
              final PortalRequestInfoImpl portalRequestInfo = new PortalRequestInfoImpl();
              IPortletWindowId targetedPortletWindowId = null;
              PortletRequestInfoImpl targetedPortletRequestInfo = null;
  
              final String[] requestPathParts = SLASH_PATTERN.split(requestPath);
  
              UrlState requestedUrlState = null;
              ParseStep parseStep = ParseStep.FOLDER;
              for (int pathPartIndex = 0; pathPartIndex < requestPathParts.length; pathPartIndex++) {
                  String pathPart = requestPathParts[pathPartIndex];
  
                  logger.trace("In parseStep {} considering pathPart [{}].", parseStep, pathPart);
  
                  if (StringUtils.isEmpty(pathPart)) {
                      continue;
                  }
  
                  switch (parseStep) {
                      case FOLDER:
                          {
                              parseStep = ParseStep.PORTLET;
  
                              if (FOLDER_PATH_PREFIX.equals(pathPart)) {
  
                                  logger.trace(
                                          "Skipping adding {} to the folders deque "
                                                  + "because it is simply the folder path prefix.",
                                          pathPart);
  
                                  pathPartIndex++;
  
                                  final LinkedList<String> folders = new LinkedList<String>();
                                  for (; pathPartIndex < requestPathParts.length; pathPartIndex++) {
                                      pathPart = requestPathParts[pathPartIndex];
  
                                      if (PORTLET_PATH_PREFIX.equals(pathPart)) {
                                          logger.trace(
                                                  "Found the portlet part of the path "
                                                          + "demarked by portlet path prefix [{}]; "
                                                          + "stepping back one path part to finish folder processing",
                                                  pathPart);
                                          pathPartIndex--;
                                          break;
                                      } else {
  
                                          if (pathPart.endsWith(REQUEST_TYPE_SUFFIX)) {
                                              logger.trace(
                                                      "Found the end of the folder path with pathPart [{}];"
                                                              + " stepping back one, checking for state, "
                                                              + "and finishing folder parsing",
                                                      pathPart);
                                              pathPartIndex--;
                                              pathPart = requestPathParts[pathPartIndex];
  
                                              // If a state was added to the folder list remove it and
                                              // step back one so
                                              // other code can handle it
                                              if (UrlState.valueOfIngoreCase(pathPart, null)
                                                      != null) {
                                                  logger.trace(
                                                          "A state was added to the end of folder list {};"
                                                                  + " removing it.",
                                                          folders);
                                                  folders.removeLast();
                                                  pathPartIndex--;
                                              }
                                              break;
                                          }
                                      }
  
                                      logger.trace("Adding pathPart [{}] to folders.", pathPart);
                                      folders.add(pathPart);
                                  }
  
                                  logger.trace("Folders is [{}]", folders);
                                  if (folders.size() > 0) {
                                      final String targetedLayoutNodeId =
                                              urlNodeSyntaxHelper.getLayoutNodeForFolderNames(
                                                      request, folders);
                                      portalRequestInfo.setTargetedLayoutNodeId(targetedLayoutNodeId);
                                  }
                                  break;
                              }
                          }
                          // fall through
                      case PORTLET:
                          {
                              parseStep = ParseStep.STATE;
  
                              final String targetedLayoutNodeId =
                                      portalRequestInfo.getTargetedLayoutNodeId();
  
                              if (PORTLET_PATH_PREFIX.equals(pathPart)) {
                                  if (++pathPartIndex < requestPathParts.length) {
                                      pathPart = requestPathParts[pathPartIndex];
  
                                      targetedPortletWindowId =
                                              urlNodeSyntaxHelper.getPortletForFolderName(
                                                      request, targetedLayoutNodeId, pathPart);
                                  }
  
                                  break;
                              }
  
                              // See if a portlet was targeted by parameter
                              final String[] targetedPortletIds =
                                      parameterMap.remove(PARAM_TARGET_PORTLET);
                              if (targetedPortletIds != null && targetedPortletIds.length > 0) {
                                  final String targetedPortletString = targetedPortletIds[0];
                                  targetedPortletWindowId =
                                          urlNodeSyntaxHelper.getPortletForFolderName(
                                                  request,
                                                  targetedLayoutNodeId,
                                                  targetedPortletString);
                              }
                          }
                          // fall through
                      case STATE:
                          {
                              parseStep = ParseStep.TYPE;
  
                              // States other than the default only make sense if a portlet is being
                              // targeted
                              if (targetedPortletWindowId == null) {
                                  break;
                              }
  
                              requestedUrlState = UrlState.valueOfIngoreCase(pathPart, null);
  
                              // Set the URL state
                              if (requestedUrlState != null) {
                                  portalRequestInfo.setUrlState(requestedUrlState);
  
                                  // If the request is stateless
                                  if (statelessUrlStates.contains(requestedUrlState)) {
                                      final IPortletWindow statelessPortletWindow =
                                              this.portletWindowRegistry
                                                      .getOrCreateStatelessPortletWindow(
                                                              request, targetedPortletWindowId);
                                      targetedPortletWindowId =
                                              statelessPortletWindow.getPortletWindowId();
                                  }
  
                                  // Create the portlet request info
                                  targetedPortletRequestInfo =
                                          portalRequestInfo.getPortletRequestInfo(
                                                  targetedPortletWindowId);
                                  portalRequestInfo.setTargetedPortletWindowId(
                                          targetedPortletWindowId);
  
                                  // Set window state based on URL State first then look for the
                                  // window state parameter
                                  switch (requestedUrlState) {
                                      case MAX:
                                          {
                                              targetedPortletRequestInfo.setWindowState(
                                                      WindowState.MAXIMIZED);
                                          }
                                          break;
  
                                      case DETACHED:
                                          {
                                              targetedPortletRequestInfo.setWindowState(
                                                      IPortletRenderer.DETACHED);
                                          }
                                          break;
  
                                      case EXCLUSIVE:
                                          {
                                              targetedPortletRequestInfo.setWindowState(
                                                      IPortletRenderer.EXCLUSIVE);
                                          }
                                          break;
  
                                      default:
                                          // Nothing to do -- some cases don't need treatment
                                  }
  
                                  break;
                              }
                          }
                          // fall through
                      case TYPE:
                          {
                              parseStep = ParseStep.COMPLETE;
  
                              if (pathPartIndex == requestPathParts.length - 1
                                      && pathPart.endsWith(REQUEST_TYPE_SUFFIX)
                                      && pathPart.length() > REQUEST_TYPE_SUFFIX.length()) {
                                  final String urlTypePart =
                                          pathPart.substring(
                                                  0,
                                                  pathPart.length() - REQUEST_TYPE_SUFFIX.length());
                                  final UrlType urlType;
  
                                  // Handle inline resourceIds, look for a . in the request type
                                  // string and use the suffix as the urlType
                                  final int lastPeriod = urlTypePart.lastIndexOf('.');
                                  if (lastPeriod >= 0 && lastPeriod < urlTypePart.length()) {
                                      final String urlTypePartSuffix =
                                              urlTypePart.substring(lastPeriod + 1);
                                      urlType = UrlType.valueOfIngoreCase(urlTypePartSuffix, null);
                                      if (urlType == UrlType.RESOURCE
                                              && targetedPortletRequestInfo != null) {
                                          final String resourceId =
                                                  urlTypePart.substring(0, lastPeriod);
                                          targetedPortletRequestInfo.setResourceId(resourceId);
                                      }
                                  } else {
                                      urlType = UrlType.valueOfIngoreCase(urlTypePart, null);
                                  }
  
                                  if (urlType != null) {
                                      portalRequestInfo.setUrlType(urlType);
                                      break;
                                  }
                              }
                          }
                          // fall through
                      default:
                          // Nothing to do -- some cases don't need treatment
                  }
              }
  
              // If a targeted portlet window ID is found but no targeted portlet request info has
              // been retrieved yet, set it up
              if (targetedPortletWindowId != null && targetedPortletRequestInfo == null) {
                  targetedPortletRequestInfo =
                          portalRequestInfo.getPortletRequestInfo(targetedPortletWindowId);
                  portalRequestInfo.setTargetedPortletWindowId(targetedPortletWindowId);
              }
  
              // Get the set of portlet window ids that also have parameters on the url
              final String[] additionalPortletIdArray = parameterMap.remove(PARAM_ADDITIONAL_PORTLET);
              final Set<String> additionalPortletIds =
                      Sets.newHashSet(
                              additionalPortletIdArray != null
                                      ? additionalPortletIdArray
                                      : new String[0]);
  
              // Used if there is delegation to capture form-submit and other non-prefixed parameters
              // Map of parent id to delegate id
              final Map<IPortletWindowId, IPortletWindowId> delegateIdMappings =
                      new LinkedHashMap<IPortletWindowId, IPortletWindowId>(0);
  
              // Parse all remaining parameters from the request
              final Set<Entry<String, String[]>> parameterEntrySet = parameterMap.entrySet();
              for (final Iterator<Entry<String, String[]>> parameterEntryItr =
                              parameterEntrySet.iterator();
                      parameterEntryItr.hasNext(); ) {
                  final Entry<String, String[]> parameterEntry = parameterEntryItr.next();
  
                  final String name = parameterEntry.getKey();
                  final List<String> values = Arrays.asList(parameterEntry.getValue());
  
                  /* NOTE: continues are being used to allow fall-through behavior like a switch statement would provide */
  
                  // Portal Parameters, just need to remove the prefix
                  if (name.startsWith(PORTAL_PARAM_PREFIX)) {
                      final Map<String, List<String>> portalParameters =
                              portalRequestInfo.getPortalParameters();
                      portalParameters.put(
                              this.safeSubstringAfter(PORTAL_PARAM_PREFIX, name), values);
                      parameterEntryItr.remove();
                      continue;
                  }
  
                  // Generic portlet parameters, have to remove the prefix and see if there was a
                  // portlet windowId between the prefix and parameter name
                  if (name.startsWith(PORTLET_PARAM_PREFIX)) {
                      final Tuple<String, IPortletWindowId> portletParameterParts =
                              this.parsePortletParameterName(request, name, additionalPortletIds);
                      final IPortletWindowId portletWindowId = portletParameterParts.second;
                      final String paramName = portletParameterParts.first;
  
                      // Get the portlet parameter map to add the parameter to
                      final Map<String, List<String>> portletParameters;
                      if (portletWindowId == null) {
                          if (targetedPortletRequestInfo == null) {
                              this.logger.warn(
                                      "Parameter "
                                              + name
                                              + " is for the targeted portlet but no portlet is targeted by the request. The parameter will be ignored. Value: "
                                              + values);
                              parameterEntryItr.remove();
                              break;
                          }
  
                          portletParameters = targetedPortletRequestInfo.getPortletParameters();
                      } else {
                          final PortletRequestInfoImpl portletRequestInfoImpl =
                                  portalRequestInfo.getPortletRequestInfo(portletWindowId);
                          portletParameters = portletRequestInfoImpl.getPortletParameters();
                      }
  
                      portletParameters.put(paramName, values);
                      parameterEntryItr.remove();
                      continue;
                  }
  
                  // Portlet control parameters are either used directly or as a prefix to a windowId.
                  // Use the
                  // SuffixedPortletParameter to simplify their parsing
                  for (final SuffixedPortletParameter suffixedPortletParameter :
                          SuffixedPortletParameter.values()) {
                      final String parameterPrefix = suffixedPortletParameter.getParameterPrefix();
                      // Skip to the next parameter prefix if the current doesn't match
                      if (!name.startsWith(parameterPrefix)) {
                          continue;
                      }
  
                      // All of these parameters require at least one value
                      if (values.isEmpty()) {
                          this.logger.warn(
                                  "Ignoring parameter {} as it must have a value. Value: {}",
                                  name,
                                  values);
                          break;
                      }
  
                      // Verify the parameter is being used on the correct type of URL
                      final Set<UrlType> validUrlTypes = suffixedPortletParameter.getValidUrlTypes();
                      if (!validUrlTypes.contains(portalRequestInfo.getUrlType())) {
                          this.logger.warn(
                                  "Ignoring parameter {} as it is only valid for {} requests and this is a "
                                          + "{} request. Value: {}",
                                  name,
                                  validUrlTypes,
                                  portalRequestInfo.getUrlType(),
                                  values);
                          break;
                      }
  
                      // Determine the portlet window and request info the parameter targets
                      final IPortletWindowId portletWindowId =
                              this.parsePortletWindowIdSuffix(
                                      request, parameterPrefix, additionalPortletIds, name);
                      final PortletRequestInfoImpl portletRequestInfo =
                              getTargetedPortletRequestInfo(
                                      portalRequestInfo, targetedPortletRequestInfo, portletWindowId);
                      if (portletRequestInfo == null) {
                          this.logger.warn(
                                  "Parameter {} is for the targeted portlet but no portlet is targeted"
                                          + " by the request. The parameter will be ignored. Value: {}",
                                  name,
                                  values);
                          break;
                      }
  
                      parameterEntryItr.remove();
  
                      // Use the enum helper to store the parameter values on the request info
                      suffixedPortletParameter.updateRequestInfo(
                              request,
                              portletWindowRegistry,
                              portletRequestInfo,
                              values,
                              delegateIdMappings);
                      break;
                  }
              }
  
              // Any non-namespaced parameters still need processing?
              if (!parameterMap.isEmpty()) {
                  // If the parameter was not ignored by a previous parser add it to whatever was
                  // targeted (portlet or portal)
                  final Map<String, List<String>> parameters;
                  if (!delegateIdMappings.isEmpty()) {
                      // Resolve the last portlet window in the chain of delegation
                      PortletRequestInfoImpl delegatePortletRequestInfo = null;
                      for (final IPortletWindowId delegatePortletWindowId :
                              delegateIdMappings.values()) {
                          if (!delegateIdMappings.containsKey(delegatePortletWindowId)) {
                              delegatePortletRequestInfo =
                                      portalRequestInfo.getPortletRequestInfo(
                                              delegatePortletWindowId);
                              break;
                          }
                      }
  
                      if (delegatePortletRequestInfo != null) {
                          parameters = delegatePortletRequestInfo.getPortletParameters();
                      } else {
                          this.logger.warn(
                                  "No root delegate portlet could be resolved, non-namespaced parameters"
                                          + " will be sent to the targeted portlet. THIS SHOULD NEVER HAPPEN. Delegate"
                                          + " parent/child mapping: {}",
                                  delegateIdMappings);
  
                          if (targetedPortletRequestInfo != null) {
                              parameters = targetedPortletRequestInfo.getPortletParameters();
                          } else {
                              parameters = portalRequestInfo.getPortalParameters();
                          }
                      }
                  } else if (targetedPortletRequestInfo != null) {
                      parameters = targetedPortletRequestInfo.getPortletParameters();
                  } else {
                      parameters = portalRequestInfo.getPortalParameters();
                  }
  
                  ParameterMap.putAllList(parameters, parameterMap);
              }
  
              // If a portlet is targeted but no layout node is targeted must be maximized
              if (targetedPortletRequestInfo != null
                      && portalRequestInfo.getTargetedLayoutNodeId() == null
                      && (requestedUrlState == null || requestedUrlState == UrlState.NORMAL)) {
                  portalRequestInfo.setUrlState(UrlState.MAX);
                  targetedPortletRequestInfo.setWindowState(WindowState.MAXIMIZED);
              }
  
              // Make the request info object read-only, once parsed the request info should be static
              portalRequestInfo.makeReadOnly();
  
              request.setAttribute(PORTAL_REQUEST_INFO_ATTR, portalRequestInfo);
  
              logger.debug("Finished building requestInfo: {}", portalRequestInfo);
  
              return portalRequestInfo;
          } finally {
              request.removeAttribute(PORTAL_REQUEST_PARSING_IN_PROGRESS_ATTR);
          }
      }