@Override
  public List<String> getBoundLogicalRoleNames(
      Session session, ITenant tenant, List<String> runtimeRoleNames)
      throws NamespaceException, RepositoryException {
    if ((tenant == null) || (tenant.getId() == null)) {
      return getBoundLogicalRoleNames(session, runtimeRoleNames);
    }

    if (!TenantUtils.isAccessibleTenant(tenant)) {
      return new ArrayList<String>();
    }

    final List<String> uncachedRuntimeRoleNames = new ArrayList<String>();
    final Set<String> cachedBoundLogicalRoleNames = new HashSet<String>();
    for (String runtimeRoleName : runtimeRoleNames) {
      String roleName = tenantedRoleNameUtils.getPrincipleName(runtimeRoleName);
      String roleId = tenantedRoleNameUtils.getPrincipleId(tenant, runtimeRoleName);
      Object fromRegionCache =
          cacheManager.getFromRegionCache(LOGICAL_ROLE_BINDINGS_REGION, roleId);
      if (fromRegionCache != null) {
        cachedBoundLogicalRoleNames.addAll((Collection<String>) fromRegionCache);
      } else {
        uncachedRuntimeRoleNames.add(roleName);
      }
    }
    if (uncachedRuntimeRoleNames.isEmpty()) {
      // no need to hit the repo
      return new ArrayList<String>(cachedBoundLogicalRoleNames);
    }

    PentahoJcrConstants pentahoJcrConstants = new PentahoJcrConstants(session);
    final String phoNsPrefix =
        session.getNamespacePrefix(PentahoJcrConstants.PHO_NS) + ":"; // $NON-NLS-1$
    final String onlyPentahoPattern = phoNsPrefix + "*"; // $NON-NLS-1$
    HashMultimap<String, String> boundLogicalRoleNames = HashMultimap.create();
    Node runtimeRolesFolderNode = getRuntimeRolesFolderNode(session, tenant);
    NodeIterator runtimeRoleNodes = runtimeRolesFolderNode.getNodes(onlyPentahoPattern);
    if (!runtimeRoleNodes.hasNext()) {
      // no bindings setup yet; fall back on bootstrap bindings
      for (String runtimeRoleName : uncachedRuntimeRoleNames) {
        String roleId = tenantedRoleNameUtils.getPrincipleId(tenant, runtimeRoleName);
        if (bootstrapRoleBindings.containsKey(runtimeRoleName)) {
          boundLogicalRoleNames.putAll(roleId, bootstrapRoleBindings.get(runtimeRoleName));
        }
      }
    } else {
      for (String runtimeRoleName : uncachedRuntimeRoleNames) {
        if (NodeHelper.hasNode(runtimeRolesFolderNode, phoNsPrefix, runtimeRoleName)) {
          Node runtimeRoleFolderNode =
              NodeHelper.getNode(runtimeRolesFolderNode, phoNsPrefix, runtimeRoleName);
          if (runtimeRoleFolderNode.hasProperty(pentahoJcrConstants.getPHO_BOUNDROLES())) {
            Value[] values =
                runtimeRoleFolderNode
                    .getProperty(pentahoJcrConstants.getPHO_BOUNDROLES())
                    .getValues();
            String roleId = tenantedRoleNameUtils.getPrincipleId(tenant, runtimeRoleName);
            for (Value value : values) {
              boundLogicalRoleNames.put(roleId, value.getString());
            }
          }
        }
      }
    }
    // now add in immutable bound logical role names
    for (String runtimeRoleName : uncachedRuntimeRoleNames) {
      if (immutableRoleBindings.containsKey(runtimeRoleName)) {
        String roleId = tenantedRoleNameUtils.getPrincipleId(tenant, runtimeRoleName);
        boundLogicalRoleNames.putAll(roleId, immutableRoleBindingNames.get(runtimeRoleName));
      }
    }

    // update cache
    Map<String, Collection<String>> stringCollectionMap = boundLogicalRoleNames.asMap();
    for (Entry<String, Collection<String>> stringCollectionEntry : stringCollectionMap.entrySet()) {
      cacheManager.putInRegionCache(
          LOGICAL_ROLE_BINDINGS_REGION,
          stringCollectionEntry.getKey(),
          stringCollectionEntry.getValue());
    }

    // now add in those runtime roles that have no bindings to the cache
    for (String runtimeRoleName : uncachedRuntimeRoleNames) {
      String roleId = tenantedRoleNameUtils.getPrincipleId(tenant, runtimeRoleName);

      if (cacheManager.getFromRegionCache(LOGICAL_ROLE_BINDINGS_REGION, roleId) == null) {
        cacheManager.putInRegionCache(
            LOGICAL_ROLE_BINDINGS_REGION, roleId, Collections.emptyList());
      }
    }

    // combine cached findings plus ones from repo
    Set<String> res = new HashSet<String>();
    res.addAll(cachedBoundLogicalRoleNames);
    res.addAll(boundLogicalRoleNames.values());
    return new ArrayList<String>(res);
  }