docs: @Async
      public Future<ImmutableSet<MarketplaceEntry>> loadMarketplaceEntriesFor(
              final IPerson user, final Set<PortletCategory> categories) {
  
          final IAuthorizationPrincipal principal =
                  AuthorizationPrincipalHelper.principalFromUser(user);
  
          List<IPortletDefinition> allDisplayablePortletDefinitions =
                  this.portletDefinitionRegistry.getAllPortletDefinitions();
  
          if (!categories.isEmpty()) {
              // Indicates we plan to restrict portlets displayed in the Portlet
              // Marketplace to those that belong to one or more specified groups.
              Element portletDefinitionsElement = marketplaceCategoryCache.get(categories);
              if (portletDefinitionsElement == null) {
  
                  /*
                   * Collection not in cache -- need to recreate it
                   */
  
                  // Gather the complete collection of allowable categories (specified categories &
                  // their descendants)
                  final Set<PortletCategory> allSpecifiedAndDecendantCategories = new HashSet<>();
                  for (PortletCategory pc : categories) {
                      collectSpecifiedAndDescendantCategories(pc, allSpecifiedAndDecendantCategories);
                  }
  
                  // Filter portlets that match the criteria
                  Set<IPortletDefinition> filteredPortletDefinitions = new HashSet<>();
                  for (final IPortletDefinition portletDefinition :
                          allDisplayablePortletDefinitions) {
                      final Set<PortletCategory> parents =
                              portletCategoryRegistry.getParentCategories(portletDefinition);
                      for (final PortletCategory parent : parents) {
                          if (allSpecifiedAndDecendantCategories.contains(parent)) {
                              filteredPortletDefinitions.add(portletDefinition);
                              break;
                          }
                      }
                  }
  
                  portletDefinitionsElement =
                          new Element(categories, new ArrayList<>(filteredPortletDefinitions));
                  marketplaceCategoryCache.put(portletDefinitionsElement);
              }
              allDisplayablePortletDefinitions =
                      (List<IPortletDefinition>) portletDefinitionsElement.getObjectValue();
          }
  
          final Set<MarketplaceEntry> visiblePortletDefinitions = new HashSet<>();
  
          for (final IPortletDefinition portletDefinition : allDisplayablePortletDefinitions) {
  
              if (mayBrowsePortlet(principal, portletDefinition)) {
                  final MarketplacePortletDefinition marketplacePortletDefinition =
                          getOrCreateMarketplacePortletDefinition(portletDefinition);
                  final MarketplaceEntry entry =
                          new MarketplaceEntry(marketplacePortletDefinition, user);
  
                  // flag whether this use can add the portlet...
                  boolean canAdd = mayAddPortlet(user, portletDefinition);
                  entry.setCanAdd(canAdd);
  
                  visiblePortletDefinitions.add(entry);
              }
          }
  
          logger.trace(
                  "These portlet definitions {} are browseable by {}.",
                  visiblePortletDefinitions,
                  user);
  
          Future<ImmutableSet<MarketplaceEntry>> result =
                  new AsyncResult<>(ImmutableSet.copyOf(visiblePortletDefinitions));
          Element cacheElement = new Element(user.getUserName(), result);
          marketplaceUserPortletDefinitionCache.put(cacheElement);
  
          return result;
      }