class AuthorizationTokenIssuer {

    public AuthorizationToken issueTokens(OAuth2Request request, ClientRegistration clientRegistration,
            ResourceOwner resourceOwner, Set<String> authorizationScope, OAuth2ProviderSettings providerSettings)
            throws InvalidClientException, UnsupportedResponseTypeException, ServerException, InvalidScopeException,
            NotFoundException {

        //issue tokens
        final Set<String> requestedResponseTypes =
                splitResponseType(request.<String>getParameter(RESPONSE_TYPE));
        if (Utils.isEmpty(requestedResponseTypes)) {
            logger.debug("Response type is not supported");
            throw new UnsupportedResponseTypeException("Response type is not supported");
        }

        final Set<String> validatedScope = providerSettings.validateAccessTokenScope(clientRegistration,
                authorizationScope, request);

        final Map<String, ResponseTypeHandler> allowedResponseTypes = providerSettings.getAllowedResponseTypes();

        final String tokenType = clientRegistration.getAccessTokenType();
        final String clientId = clientRegistration.getClientId();
        final String redirectUri = request.getParameter(REDIRECT_URI);
        final String nonce = request.getParameter(OAuth2Constants.Custom.NONCE);
        final String codeChallenge = request.getParameter(OAuth2Constants.Custom.CODE_CHALLENGE);
        final String codeChallengeMethod = request.getParameter(OAuth2Constants.Custom.CODE_CHALLENGE_METHOD);

        final Map<String, Token> tokens = new HashMap<String, Token>();
        boolean returnAsFragment = false;

        final List<String> sortedResponseTypes =
                Utils.asSortedList(requestedResponseTypes,
                        new KeyStringComparator(OAuth2Constants.JWTTokenParams.ID_TOKEN));

        for (final String responseType : sortedResponseTypes) {

            if (Utils.isEmpty(responseType)) {
                throw new UnsupportedResponseTypeException("Response type is not supported");
            }

            final ResponseTypeHandler responseTypeHandler = allowedResponseTypes.get(responseType);

            final Map.Entry<String, Token> token = responseTypeHandler.handle(tokenType, validatedScope,
                    resourceOwner, clientId, redirectUri, nonce, request, codeChallenge, codeChallengeMethod);

            if (token != null) {

                if (tokens.containsKey(token.getKey())) {
                    logger.debug("Returning multiple response types with the same url value");
                    throw new UnsupportedResponseTypeException("Returning multiple response types with the same url "
                            + "value");
                }

                tokens.put(token.getKey(), token.getValue());

                if (!returnAsFragment) {
                    final OAuth2Constants.UrlLocation returnLocation = responseTypeHandler.getReturnLocation();
                    returnAsFragment = OAuth2Constants.UrlLocation.FRAGMENT.equals(returnLocation);
                }

            }
        }

        final Map<String, String> tokenMap = flattenTokens(tokens);

        //plugin point for provider to add additional entries to tokenMap
        final Map<String, String> additionalData = providerSettings.additionalDataToReturnFromAuthorizeEndpoint(
                Collections.unmodifiableMap(tokens), request);
        if (!Utils.isEmpty(additionalData)) {
            final String returnLoc = additionalData.remove("returnLocation");
            if (!Utils.isEmpty(returnLoc)) {
                final OAuth2Constants.UrlLocation returnLocation =
                        OAuth2Constants.UrlLocation.valueOf(returnLoc.toUpperCase());
                if (!returnAsFragment && OAuth2Constants.UrlLocation.FRAGMENT.equals(returnLocation)) {
                    returnAsFragment = true;
                }
            }
            tokenMap.putAll(additionalData);
        }

        // Always echo scope back to the requester even if identical - consistent with access_token endpoint.
        tokenMap.put(SCOPE, Utils.joinScope(validatedScope));

        if (request.getParameter(STATE) != null) {
            tokenMap.put(STATE, request.<String>getParameter(STATE));
        }

        return new AuthorizationToken(tokenMap, returnAsFragment);
    }

}