ExceptionUtil.java

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.core.amqp.implementation;

import com.azure.core.amqp.exception.AmqpErrorCondition;
import com.azure.core.amqp.exception.AmqpErrorContext;
import com.azure.core.amqp.exception.AmqpException;
import com.azure.core.amqp.exception.AmqpResponseCode;

import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Utility class to map AMQP status codes and error conditions to an exception.
 */
public final class ExceptionUtil {
    private static final String AMQP_REQUEST_FAILED_ERROR = "status-code: %s, status-description: %s";
    private static final Pattern ENTITY_NOT_FOUND_PATTERN =
        Pattern.compile("The messaging entity .* could not be found");

    /**
     * Creates an {@link AmqpException} or Exception based on the {@code errorCondition} from the AMQP request.
     *
     * @param errorCondition The error condition string.
     * @param description The error message.
     * @param errorContext The context that this error occurred in.
     * @return An exception that maps to the {@code errorCondition} provided.
     * @throws IllegalArgumentException when 'errorCondition' is {@code null} or empty, cannot be translated into an
     *     {@link AmqpErrorCondition}, or cannot be determined whether the {@link AmqpErrorCondition} is transient or
     *     not.
     * @see AmqpErrorCondition
     */
    public static Exception toException(String errorCondition, String description, AmqpErrorContext errorContext) {
        if (errorCondition == null) {
            throw new IllegalArgumentException("'null' errorCondition cannot be translated to EventHubException");
        }

        final AmqpErrorCondition condition = AmqpErrorCondition.fromString(errorCondition);
        if (condition == null) {
            return new AmqpException(false, String.format("errorCondition[%s]. description[%s]",
                errorCondition, description), errorContext);
        }

        boolean isTransient;
        switch (condition) {
            case TIMEOUT_ERROR:
            case SERVER_BUSY_ERROR:
            case INTERNAL_ERROR:
            case LINK_DETACH_FORCED:
            case CONNECTION_FORCED:
            case CONNECTION_FRAMING_ERROR:
            case PROTON_IO:
                isTransient = true;
                break;
            case ENTITY_DISABLED_ERROR:
            case LINK_STOLEN:
            case UNAUTHORIZED_ACCESS:
            case LINK_PAYLOAD_SIZE_EXCEEDED:
            case ARGUMENT_ERROR:
            case ARGUMENT_OUT_OF_RANGE_ERROR:
            case PARTITION_NOT_OWNED_ERROR:
            case STORE_LOCK_LOST_ERROR:
            case RESOURCE_LIMIT_EXCEEDED:
            case OPERATION_CANCELLED:
            case MESSAGE_LOCK_LOST:
            case SESSION_LOCK_LOST:
            case SESSION_CANNOT_BE_LOCKED:
            case ENTITY_ALREADY_EXISTS:
            case MESSAGE_NOT_FOUND:
            case SESSION_NOT_FOUND:
                isTransient = false;
                break;
            case NOT_IMPLEMENTED:
            case NOT_ALLOWED:
                return new UnsupportedOperationException(description);
            case NOT_FOUND:
                return distinguishNotFound(description, errorContext);
            default:
                throw new IllegalArgumentException(String.format(Locale.ROOT, "This condition '%s' is not known.",
                    condition));
        }

        return new AmqpException(isTransient, condition, description, errorContext);
    }

    /**
     * Given an AMQP response code, it maps it to an exception.
     *
     * @param statusCode AMQP response code.
     * @param statusDescription Message associated with response.
     * @param errorContext The context that this error occurred in.
     * @return An exception that maps to that status code.
     */
    public static Exception amqpResponseCodeToException(int statusCode, String statusDescription,
        AmqpErrorContext errorContext) {

        final AmqpResponseCode amqpResponseCode = AmqpResponseCode.fromValue(statusCode);
        final String message = String.format(AMQP_REQUEST_FAILED_ERROR, statusCode, statusDescription);

        if (amqpResponseCode == null) {
            return new AmqpException(true, message, errorContext);
        }

        switch (amqpResponseCode) {
            case BAD_REQUEST:
                return new IllegalArgumentException(message);
            case NOT_FOUND:
                return distinguishNotFound(statusDescription, errorContext);
            case FORBIDDEN:
                return new AmqpException(false, AmqpErrorCondition.RESOURCE_LIMIT_EXCEEDED, message, errorContext);
            case UNAUTHORIZED:
                return new AmqpException(false, AmqpErrorCondition.UNAUTHORIZED_ACCESS, message, errorContext);
            default:
                return new AmqpException(true, message, errorContext);
        }
    }

    private static AmqpException distinguishNotFound(String message, AmqpErrorContext errorContext) {
        final Matcher m = ENTITY_NOT_FOUND_PATTERN.matcher(message);
        if (m.find()) {
            return new AmqpException(false, AmqpErrorCondition.NOT_FOUND, message, errorContext);
        } else {
            return new AmqpException(true, AmqpErrorCondition.NOT_FOUND,
                String.format(AMQP_REQUEST_FAILED_ERROR, AmqpResponseCode.NOT_FOUND, message),
                errorContext);
        }
    }
}