class AthenzRoleFilter extends JsonSecurityRequestFilterBase {

    Set<Role> roles(AthenzPrincipal principal, URI uri) throws Exception {
        Path path = new Path(uri);

        path.matches("/application/v4/tenant/{tenant}/{*}");
        Optional<Tenant> tenant = Optional.ofNullable(path.get("tenant")).map(TenantName::from).flatMap(tenants::get);

        path.matches("/application/v4/tenant/{tenant}/application/{application}/{*}");
        Optional<ApplicationName> application = Optional.ofNullable(path.get("application")).map(ApplicationName::from);

        AthenzIdentity identity = principal.getIdentity();

        Set<Role> roleMemberships = new CopyOnWriteArraySet<>();
        List<Future<?>> futures = new ArrayList<>();

        futures.add(executor.submit(() -> {
            if (athenz.hasHostedOperatorAccess(identity))
                roleMemberships.add(Role.hostedOperator());
        }));

        futures.add(executor.submit(() -> {
            if (athenz.hasHostedSupporterAccess(identity))
                roleMemberships.add(Role.hostedSupporter());
        }));

        futures.add(executor.submit(() -> {
            // Add all tenants that are accessible for this request
            athenz.accessibleTenants(tenants.asList(), new Credentials(principal))
                  .forEach(accessibleTenant -> roleMemberships.add(Role.athenzTenantAdmin(accessibleTenant.name())));
        }));

        if (     identity.getDomain().equals(SCREWDRIVER_DOMAIN)
            &&   application.isPresent()
            &&   tenant.isPresent()
            && ! tenant.get().name().value().equals("sandbox"))
            futures.add(executor.submit(() -> {
                if (   tenant.get().type() == Tenant.Type.athenz
                    && hasDeployerAccess(identity, ((AthenzTenant) tenant.get()).domain(), application.get()))
                    roleMemberships.add(Role.buildService(tenant.get().name(), application.get()));
            }));

        futures.add(executor.submit(() -> {
            if (athenz.hasSystemFlagsAccess(identity, /*dryrun*/false))
                roleMemberships.add(Role.systemFlagsDeployer());
        }));

        futures.add(executor.submit(() -> {
            if (athenz.hasPaymentCallbackAccess(identity))
                roleMemberships.add(Role.paymentProcessor());
        }));

        futures.add(executor.submit(() -> {
            if (athenz.hasAccountingAccess(identity))
                roleMemberships.add(Role.hostedAccountant());
        }));

        // Run last request in handler thread to avoid creating extra thread.
        if (athenz.hasSystemFlagsAccess(identity, /*dryrun*/true))
            roleMemberships.add(Role.systemFlagsDryrunner());

        for (Future<?> future : futures)
            future.get(30, TimeUnit.SECONDS);

        return roleMemberships.isEmpty()
                ? Set.of(Role.everyone())
                : Set.copyOf(roleMemberships);
    }

    @Inject public  AthenzRoleFilter(AthenzClientFactory athenzClientFactory, Controller controller);

}