| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| using System; |
| using System.Collections.Generic; |
| using Apache.Qpid.Proton.Client.Exceptions; |
| using Apache.Qpid.Proton.Engine.Sasl; |
| using Apache.Qpid.Proton.Types; |
| using Apache.Qpid.Proton.Types.Transactions; |
| using Apache.Qpid.Proton.Types.Transport; |
| |
| namespace Apache.Qpid.Proton.Client.Implementation |
| { |
| /// <summary> |
| /// Exception support API for creating or passing through client |
| /// exception types and or wrapping when a fatal vs non-fatal type |
| /// needs to be presented. |
| /// </summary> |
| public static class ClientExceptionSupport |
| { |
| /// <summary> |
| /// Checks the given cause to determine if it's already an ClientIOException type and |
| /// if not creates a new ClientIOException to wrap it. |
| /// </summary> |
| /// <param name="cause">The exception to operate on</param> |
| /// <returns>A client exception that is produced using the input exception instance</returns> |
| public static ClientIOException CreateOrPassthroughFatal(Exception cause) |
| { |
| if (cause is ClientIOException) |
| { |
| return (ClientIOException)cause; |
| } |
| |
| if (cause.InnerException is ClientIOException) |
| { |
| return (ClientIOException)cause.InnerException; |
| } |
| |
| string message = cause.Message; |
| if (string.IsNullOrEmpty(message)) |
| { |
| message = cause.ToString(); |
| } |
| |
| return new ClientIOException(message, cause); |
| } |
| |
| /// <summary> |
| /// Checks the given cause to determine if it's already an ClientException type and |
| /// if not creates a new ClientException to wrap it. If the inbound exception is a |
| /// fatal type then it will pass through this method untouched to preserve the fatal |
| /// status of the error. |
| /// </summary> |
| /// <param name="cause">The exception to operate on</param> |
| /// <returns>A client exception that is produced using the input exception instance</returns> |
| public static ClientException CreateNonFatalOrPassthrough(Exception cause) |
| { |
| if (cause is ClientException) |
| { |
| return (ClientException)cause; |
| } |
| |
| if (cause.InnerException is ClientException) |
| { |
| return (ClientException)cause.InnerException; |
| } |
| |
| string message = cause.Message; |
| if (string.IsNullOrEmpty(message)) |
| { |
| message = cause.ToString(); |
| } |
| |
| // TODO interrogate task exceptions for any client exception |
| |
| if (cause is TimeoutException) |
| { |
| return new ClientOperationTimedOutException(message, cause); |
| } |
| else if (cause is InvalidOperationException) |
| { |
| return new ClientIllegalStateException(message, cause); |
| } |
| else |
| { |
| return new ClientException(message, cause); |
| } |
| } |
| |
| /// <summary> |
| /// Given an ErrorCondition instance create a new Exception that best matches |
| /// the error type that indicates the connection creation failed for some reason. |
| /// </summary> |
| /// <param name="errorCondition">The proton error condition to wrap</param> |
| /// <returns>A connection remotely closed variant based on the proton error</returns> |
| public static ClientConnectionRemotelyClosedException ConvertToConnectionClosedException(ErrorCondition errorCondition) |
| { |
| ClientConnectionRemotelyClosedException remoteError; |
| |
| if (errorCondition?.Condition != null) |
| { |
| Symbol error = errorCondition.Condition; |
| string message = ExtractErrorMessage(errorCondition); |
| |
| if (error.Equals(AmqpError.UNAUTHORIZED_ACCESS)) |
| { |
| remoteError = new ClientConnectionSecurityException(message, new ClientErrorCondition(errorCondition)); |
| } |
| else if (error.Equals(ConnectionError.REDIRECT)) |
| { |
| remoteError = CreateConnectionRedirectException(error, message, errorCondition); |
| } |
| else |
| { |
| remoteError = new ClientConnectionRemotelyClosedException(message, new ClientErrorCondition(errorCondition)); |
| } |
| } |
| else |
| { |
| remoteError = new ClientConnectionRemotelyClosedException("Unknown error from remote peer"); |
| } |
| |
| return remoteError; |
| } |
| |
| /// <summary> |
| /// Given an Exception instance create a new Exception that best matches |
| /// the error type that indicates the connection creation failed for some reason. |
| /// </summary> |
| /// <param name="cause">The exception that indicates why the connection closed</param> |
| /// <returns>A connection remotely closed that indicates the reason.</returns> |
| public static ClientConnectionRemotelyClosedException ConvertToConnectionClosedException(Exception cause) |
| { |
| ClientConnectionRemotelyClosedException remoteError = null; |
| |
| if (cause is ClientConnectionRemotelyClosedException) |
| { |
| remoteError = (ClientConnectionRemotelyClosedException)cause; |
| } |
| else if (cause is SaslSystemException) |
| { |
| remoteError = new ClientConnectionSecuritySaslException( |
| cause.Message, cause, !((SaslSystemException)cause).Permanent); |
| } |
| else if (cause is SaslException) |
| { |
| remoteError = new ClientConnectionSecuritySaslException(cause.Message, cause); |
| } |
| else |
| { |
| remoteError = new ClientConnectionRemotelyClosedException(cause.Message, cause); |
| } |
| |
| return remoteError; |
| } |
| |
| /// <summary> |
| /// Given an ErrorCondition instance create a new Exception that best matches |
| /// the error type that indicates the session creation failed for some reason. |
| /// </summary> |
| /// <param name="errorCondition">The proton error condition to wrap</param> |
| /// <returns>A session remotely closed variant based on the proton error</returns> |
| public static ClientSessionRemotelyClosedException ConvertToSessionClosedException(ErrorCondition errorCondition) |
| { |
| ClientSessionRemotelyClosedException remoteError; |
| |
| if (errorCondition?.Condition != null) |
| { |
| String message = ExtractErrorMessage(errorCondition); |
| if (message == null) |
| { |
| message = "Session remotely closed without explanation"; |
| } |
| |
| remoteError = new ClientSessionRemotelyClosedException(message, new ClientErrorCondition(errorCondition)); |
| } |
| else |
| { |
| remoteError = new ClientSessionRemotelyClosedException("Session remotely closed without explanation"); |
| } |
| |
| return remoteError; |
| } |
| |
| /// <summary> |
| /// Given an ErrorCondition instance create a new Exception that best matches |
| /// the error type that indicates the link creation failed for some reason. |
| /// </summary> |
| /// <param name="errorCondition">The proton error condition to wrap</param> |
| /// <param name="defaultMessage">An error message to use if no more specific message is available</param> |
| /// <returns>A session remotely closed variant based on the proton error</returns> |
| public static ClientLinkRemotelyClosedException ConvertToLinkClosedException(ErrorCondition errorCondition, string defaultMessage) |
| { |
| ClientLinkRemotelyClosedException remoteError; |
| |
| if (errorCondition?.Condition != null) |
| { |
| string message = ExtractErrorMessage(errorCondition); |
| Symbol error = errorCondition.Condition; |
| |
| if (message == null) |
| { |
| message = defaultMessage; |
| } |
| |
| if (error.Equals(LinkError.REDIRECT)) |
| { |
| remoteError = CreateLinkRedirectException(error, message, errorCondition); |
| } |
| else |
| { |
| remoteError = new ClientLinkRemotelyClosedException(message, new ClientErrorCondition(errorCondition)); |
| } |
| } |
| else |
| { |
| remoteError = new ClientLinkRemotelyClosedException(defaultMessage); |
| } |
| |
| return remoteError; |
| } |
| |
| /// <summary> |
| /// Given an ErrorCondition instance create a new Exception that best matches |
| /// the error type that indicates a non-fatal error usually at the link level |
| /// such as link closed remotely or link create failed due to security access |
| /// issues. |
| /// </summary> |
| /// <param name="errorCondition">The proton error condition to wrap</param> |
| /// <returns>A non-fatal client exception based on the proton error</returns> |
| public static ClientException ConvertToNonFatalException(ErrorCondition errorCondition) |
| { |
| ClientException remoteError; |
| |
| if (errorCondition?.Condition != null) |
| { |
| Symbol error = errorCondition.Condition; |
| string message = ExtractErrorMessage(errorCondition); |
| |
| if (error.Equals(AmqpError.RESOURCE_LIMIT_EXCEEDED)) |
| { |
| remoteError = new ClientResourceRemotelyClosedException(message, new ClientErrorCondition(errorCondition)); |
| } |
| else if (error.Equals(AmqpError.NOT_FOUND)) |
| { |
| remoteError = new ClientResourceRemotelyClosedException(message, new ClientErrorCondition(errorCondition)); |
| } |
| else if (error.Equals(LinkError.DETACH_FORCED)) |
| { |
| remoteError = new ClientResourceRemotelyClosedException(message, new ClientErrorCondition(errorCondition)); |
| } |
| else if (error.Equals(LinkError.REDIRECT)) |
| { |
| remoteError = CreateLinkRedirectException(error, message, errorCondition); |
| } |
| else if (error.Equals(AmqpError.RESOURCE_DELETED)) |
| { |
| remoteError = new ClientResourceRemotelyClosedException(message, new ClientErrorCondition(errorCondition)); |
| } |
| else if (error.Equals(TransactionError.TRANSACTION_ROLLBACK)) |
| { |
| remoteError = new ClientTransactionRolledBackException(message); |
| } |
| else |
| { |
| remoteError = new ClientException(message); |
| } |
| } |
| else |
| { |
| remoteError = new ClientException("Unknown error from remote peer"); |
| } |
| |
| return remoteError; |
| } |
| |
| /// <summary> |
| /// Attempt to read and return the embedded error message in the given ErrorCondition. |
| /// </summary> |
| /// <param name="errorCondition">The error condition to inspect</param> |
| /// <returns>The extracted error message</returns> |
| public static string ExtractErrorMessage(ErrorCondition errorCondition) |
| { |
| string message = "Received error from remote peer without description"; |
| if (errorCondition != null) |
| { |
| if (!string.IsNullOrEmpty(errorCondition.Description)) |
| { |
| message = errorCondition.Description; |
| } |
| |
| Symbol condition = errorCondition.Condition; |
| if (condition != null) |
| { |
| message = message + " [condition = " + condition + "]"; |
| } |
| } |
| |
| return message; |
| } |
| |
| /// <summary> |
| /// When a connection redirect type exception is received this method is called to |
| /// create the appropriate redirect exception type containing the error details needed. |
| /// </summary> |
| /// <param name="error">The symbolic error to use when creating the exception</param> |
| /// <param name="message">The message to use for the created exception</param> |
| /// <param name="condition">The remote ErrorCondition to use when creating the exception</param> |
| /// <returns>A new connection remotely closed exception with redirect data if available</returns> |
| public static ClientConnectionRemotelyClosedException CreateConnectionRedirectException(Symbol error, string message, ErrorCondition condition) |
| { |
| ClientConnectionRemotelyClosedException result; |
| IReadOnlyDictionary<Symbol, object> info = condition.Info; |
| |
| if (info == null) |
| { |
| result = new ClientConnectionRemotelyClosedException( |
| message + " : Redirection information not set.", new ClientErrorCondition(condition)); |
| } |
| else |
| { |
| ClientRedirect redirect = new ClientRedirect(info); |
| |
| try |
| { |
| result = new ClientConnectionRedirectedException( |
| message, redirect.Validate(), new ClientErrorCondition(condition)); |
| } |
| catch (Exception ex) |
| { |
| result = new ClientConnectionRemotelyClosedException( |
| message + " : " + ex.Message, new ClientErrorCondition(condition)); |
| } |
| } |
| |
| return result; |
| } |
| |
| /// <summary> |
| /// When a link redirect type exception is received this method is called to create the |
| /// appropriate redirect exception type containing the error details needed. |
| /// </summary> |
| /// <param name="error">The symbolic error to use when creating the exception</param> |
| /// <param name="message">The message to use for the created exception</param> |
| /// <param name="condition">The remote ErrorCondition to use when creating the exception</param> |
| /// <returns>A new link remotely closed exception with redirect data if available</returns> |
| public static ClientLinkRemotelyClosedException CreateLinkRedirectException(Symbol error, string message, ErrorCondition condition) |
| { |
| ClientLinkRemotelyClosedException result; |
| IReadOnlyDictionary<Symbol, object> info = condition.Info; |
| |
| if (info == null) |
| { |
| result = new ClientLinkRemotelyClosedException( |
| message + " : Redirection information not set.", new ClientErrorCondition(condition)); |
| } |
| else |
| { |
| ClientRedirect redirect = new ClientRedirect(info); |
| |
| try |
| { |
| result = new ClientLinkRedirectedException( |
| message, redirect.Validate(), new ClientErrorCondition(condition)); |
| } |
| catch (Exception ex) |
| { |
| result = new ClientLinkRemotelyClosedException( |
| message + " : " + ex.Message, new ClientErrorCondition(condition)); |
| } |
| } |
| |
| return result; |
| } |
| } |
| } |