All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.firebirdsql.gds.ng.FbExceptionBuilder Maven / Gradle / Ivy

There is a newer version: 6.0.0-beta-1
Show newest version
/*
 * Firebird Open Source JDBC Driver
 *
 * Distributable under LGPL license.
 * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * LGPL License for more details.
 *
 * This file was created by members of the firebird development team.
 * All individual contributions remain the Copyright (C) of those
 * individuals.  Contributors to this file are either listed here or
 * can be obtained from a source control history command.
 *
 * All rights reserved.
 */
package org.firebirdsql.gds.ng;

import org.firebirdsql.gds.MessageTemplate;
import org.firebirdsql.gds.ng.wire.crypt.FBSQLEncryptException;
import org.firebirdsql.jaybird.util.CollectionUtils;
import org.firebirdsql.jaybird.util.SQLExceptionChainBuilder;
import org.firebirdsql.jdbc.FBSQLExceptionInfo;
import org.firebirdsql.jdbc.SQLStateConstants;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;

import java.io.IOException;
import java.sql.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import static java.util.Objects.requireNonNull;
import static org.firebirdsql.gds.ISCConstants.*;
import static org.firebirdsql.gds.JaybirdErrorCodes.jb_connectionClosed;
import static org.firebirdsql.gds.JaybirdErrorCodes.jb_cryptAlgorithmNotAvailable;
import static org.firebirdsql.gds.JaybirdErrorCodes.jb_cryptInvalidKey;
import static org.firebirdsql.gds.JaybirdErrorCodes.jb_cryptNoCryptKeyAvailable;

/**
 * Builder for exceptions received from Firebird.
 * 

* This class is not thread-safe. *

* * @author Mark Rotteveel */ @NullMarked public final class FbExceptionBuilder { private static final String SQLSTATE_FEATURE_NOT_SUPPORTED_PREFIX = "0A"; private static final String SQLSTATE_SYNTAX_ERROR_PREFIX = "42"; private static final String SQLSTATE_CONNECTION_ERROR_PREFIX = "08"; // We generally only have a single ExceptionInformation, so presize at 1 instead of default of 10 private final List exceptionInfo = new ArrayList<>(1); public FbExceptionBuilder() { } private FbExceptionBuilder(Type type, int errorCode) { setNextExceptionInformation(type, errorCode); } /** * The (next) exception is an exception. *

* This method and related methods can be called multiple times. This * builder might produce a chained exception, but could also merge exceptions * depending on the error code and other rules internal to this builder. *

* * @param errorCode * Firebird error code * @return this FbExceptionBuilder * @see #warning(int) */ public FbExceptionBuilder exception(int errorCode) { setNextExceptionInformation(Type.EXCEPTION, errorCode); return this; } /** * Creates an exception builder with the specified error code. *

* Equivalent to calling: {@code new FbExceptionBuilder().exception(errorCode); } *

* * @param errorCode * Firebird error code * @return FbExceptionBuilder initialized with the specified error code */ public static FbExceptionBuilder forException(int errorCode) { return new FbExceptionBuilder(Type.EXCEPTION, errorCode); } /** * Creates a {@link SQLException} (or subclass) with the specified error code. *

* Equivalent to calling {@code FbExceptionBuilder.forException(errorCode).toSQLException()}. *

* * @param errorCode * Firebird error code * @return exception with message, vendor code and SQLSTATE derived from {@code errorCode} * @since 6 */ public static SQLException toException(int errorCode) { return forException(errorCode).toSQLException(); } /** * Creates an exception builder for timeout exceptions with the specified error code. *

* Equivalent to calling: {@code new FbExceptionBuilder().timeoutException(errorCode); } *

* * @param errorCode * Firebird error code * @return FbExceptionBuilder initialized with the specified error code * @since 6 */ public static FbExceptionBuilder forTimeoutException(int errorCode) { return new FbExceptionBuilder(Type.TIMEOUT, errorCode); } /** * Creates a {@link SQLException} (or subclass) for timeout exceptions with the specified error code. *

* Equivalent to calling {@code FbExceptionBuilder.forTimeoutException(errorCode).toSQLException()}. *

* * @param errorCode * Firebird error code * @return exception with message, vendor code and SQLSTATE derived from {@code errorCode} * @since 6 */ @SuppressWarnings("unused") public static SQLException toTimeoutException(int errorCode) { return forTimeoutException(errorCode).toSQLException(); } /** * Creates an exception builder for non-transient exceptions with the specified error code. *

* Equivalent to calling: {@code new FbExceptionBuilder().nonTransientException(errorCode); } *

* * @param errorCode * Firebird error code * @return FbExceptionBuilder initialized with the specified error code * @since 6 */ public static FbExceptionBuilder forNonTransientException(int errorCode) { return new FbExceptionBuilder(Type.NON_TRANSIENT, errorCode); } /** * Creates a {@link SQLException} (or subclass) for non-transient exceptions with the specified error code. *

* Equivalent to calling {@code FbExceptionBuilder.forNonTransientException(errorCode).toSQLException()}. *

* * @param errorCode * Firebird error code * @return exception with message, vendor code and SQLSTATE derived from {@code errorCode} * @since 6 */ public static SQLException toNonTransientException(int errorCode) { return forNonTransientException(errorCode).toSQLException(); } /** * Creates an exception builder for non-transient connection exceptions with the specified error code. *

* Equivalent to calling: {@code new FbExceptionBuilder().nonTransientConnectionException(errorCode); } *

* * @param errorCode * Firebird error code * @return FbExceptionBuilder initialized with the specified error code * @since 6 */ public static FbExceptionBuilder forNonTransientConnectionException(int errorCode) { return new FbExceptionBuilder(Type.NON_TRANSIENT_CONNECT, errorCode); } /** * Creates a {@link SQLException} (or subclass) for non-transient connection exceptions with the specified error * code. *

* Equivalent to calling {@code FbExceptionBuilder.forNonTransientConnectionException(errorCode).toSQLException()}. *

* * @param errorCode * Firebird error code * @return exception with message, vendor code and SQLSTATE derived from {@code errorCode} * @since 6 */ public static SQLException toNonTransientConnectionException(int errorCode) { return forNonTransientConnectionException(errorCode).toSQLException(); } /** * Creates an exception builder for transient exceptions with the specified error code. * * @param errorCode * Firebird error code * @return FbExceptionBuilder initialized with the specified error code * @since 6 */ public static FbExceptionBuilder forTransientException(int errorCode) { return new FbExceptionBuilder(Type.TRANSIENT, errorCode); } /** * Creates a {@link SQLException} (or subclass) for transient exceptions with the specified error code. *

* Equivalent to calling {@code FbExceptionBuilder.forTransientException(errorCode).toSQLException()}. *

* * @param errorCode * Firebird error code * @return exception with message, vendor code and SQLSTATE derived from {@code errorCode} * @since 6 */ public static SQLException toTransientException(int errorCode) { return forTransientException(errorCode).toSQLException(); } /** * Creates an exception builder for a warning with the specified error code. *

* Equivalent to calling: {@code new FbExceptionBuilder().warning(errorCode); } *

* * @param errorCode * Firebird error code * @return FbExceptionBuilder initialized with the specified error code */ public static FbExceptionBuilder forWarning(int errorCode) { return new FbExceptionBuilder(Type.WARNING, errorCode); } /** * Creates a {@link SQLWarning} with the specified error code. *

* Equivalent to calling {@code FbExceptionBuilder.forWarning(errorCode).toSQLException(SQLWarning.class)}. *

* * @param errorCode * Firebird error code * @return exception with message, vendor code and SQLSTATE derived from {@code errorCode} * @since 6 */ public static SQLWarning toWarning(int errorCode) { return forWarning(errorCode).toSQLException(SQLWarning.class); } private static final Map CACHED_MESSAGE_MAP = new ConcurrentHashMap<>(4, 1f, 1); /** * Gets a cached message. *

* Do not use for parameterized messages. *

* * @param errorCode * Firebird/Jaybird error code * @return cached message * @since 6 */ private static CachedMessage getCachedMessage(int errorCode) { return CACHED_MESSAGE_MAP.computeIfAbsent(errorCode, CachedMessage::of); } /** * Creates an I/O write error ({@link org.firebirdsql.gds.ISCConstants#isc_net_write_err}). * * @param e * exception cause * @return SQLException instance * @since 6 */ public static SQLException ioWriteError(IOException e) { CachedMessage error = getCachedMessage(isc_net_write_err); return stripBuilderStackTraceElements( new SQLNonTransientConnectionException(error.message, error.sqlState, isc_net_write_err, e)); } /** * Creates an I/O write error ({@link org.firebirdsql.gds.ISCConstants#isc_net_read_err}). * * @param e * exception cause * @return SQLException instance * @since 6 */ public static SQLException ioReadError(IOException e) { CachedMessage error = getCachedMessage(isc_net_read_err); return stripBuilderStackTraceElements( new SQLNonTransientConnectionException(error.message, error.sqlState, isc_net_read_err, e)); } /** * Creates a connection closed error ({@link org.firebirdsql.gds.JaybirdErrorCodes#jb_connectionClosed}). * * @return SQLException instance * @since 6 */ public static SQLException connectionClosed() { CachedMessage error = getCachedMessage(jb_connectionClosed); return stripBuilderStackTraceElements( new SQLNonTransientConnectionException(error.message, error.sqlState, jb_connectionClosed)); } /** * The (next) exception is a warning. * * @param errorCode * Firebird error code * @return this FbExceptionBuilder * @see #exception(int) */ public FbExceptionBuilder warning(int errorCode) { setNextExceptionInformation(Type.WARNING, errorCode); return this; } /** * Force the next exception to be a {@link java.sql.SQLTimeoutException}. * * @param errorCode * Firebird error code * @return this FbExceptionBuilder * @see #exception(int) */ public FbExceptionBuilder timeoutException(int errorCode) { setNextExceptionInformation(Type.TIMEOUT, errorCode); return this; } /** * Force the next exception to be a {@link java.sql.SQLNonTransientException}. * * @param errorCode * Firebird error code * @return this FbExceptionBuilder * @see #exception(int) */ @SuppressWarnings("unused") public FbExceptionBuilder nonTransientException(int errorCode) { setNextExceptionInformation(Type.NON_TRANSIENT, errorCode); return this; } /** * Force the next exception to be a {@link java.sql.SQLNonTransientConnectionException}. * * @param errorCode * Firebird error code * @return this FbExceptionBuilder * @see #exception(int) */ @SuppressWarnings("unused") public FbExceptionBuilder nonTransientConnectionException(int errorCode) { setNextExceptionInformation(Type.NON_TRANSIENT_CONNECT, errorCode); return this; } /** * Force the next exception to be a {@link java.sql.SQLTransientException}. * * @param errorCode * Firebird error code * @return this FbExceptionBuilder * @see #exception(int) */ @SuppressWarnings("unused") public FbExceptionBuilder transientException(int errorCode) { setNextExceptionInformation(Type.TRANSIENT, errorCode); return this; } /** * Adds an integer message parameter for the exception message. * * @param parameter * message parameter * @return this FbExceptionBuilder */ public FbExceptionBuilder messageParameter(int parameter) { requireExceptionInformation().addMessageParameter(parameter); return this; } /** * Adds two integer message parameters for the exception message. * * @param param1 * message parameter * @param param2 * message parameter * @return this FbExceptionBuilder * @since 5 */ public FbExceptionBuilder messageParameter(int param1, int param2) { return messageParameter((Object) param1, param2); } /** * Adds an object message parameter for the exception message (applying {@code String.valueOf(parameter)}). * * @param parameter * message parameter * @return this FbExceptionBuilder * @since 5 */ public FbExceptionBuilder messageParameter(@Nullable Object parameter) { requireExceptionInformation().addMessageParameter(parameter); return this; } /** * Adds two object message parameters for the exception message (applying {@code String.valueOf(parameter)}). * * @param param1 * message parameter * @param param2 * message parameter * @return this FbExceptionBuilder * @since 5 */ public FbExceptionBuilder messageParameter(@Nullable Object param1, @Nullable Object param2) { ExceptionInformation current = requireExceptionInformation(); current.addMessageParameter(param1); current.addMessageParameter(param2); return this; } /** * Adds object message parameters for the exception message (applying {@code String.valueOf(parameter)}). * * @param params * message parameters * @return this FbExceptionBuilder * @since 5 */ @SuppressWarnings("ForLoopReplaceableByForEach") public FbExceptionBuilder messageParameter(@Nullable Object... params) { ExceptionInformation current = requireExceptionInformation(); for (int idx = 0; idx < params.length; idx++) { current.addMessageParameter(params[idx]); } return this; } /** * Sets the SQL state. Overriding the value derived from the Firebird error code. *

* SQL State is usually derived from the errorCode. Use of this method is optional. *

* * @param sqlState * SQL State value * @return this FbExceptionBuilder */ public FbExceptionBuilder sqlState(String sqlState) { requireExceptionInformation().setSqlState(sqlState); return this; } /** * Sets the cause of the current exception. * * @param cause * Throwable with the cause * @return this FbExceptionBuilder */ public FbExceptionBuilder cause(Throwable cause) { requireExceptionInformation().setCause(cause); return this; } /** * Converts the builder to the appropriate SQLException instance (optionally with a chain of additional * exceptions). *

* When returning exception information from the status vector, it is advisable to use {@link #toFlatSQLException()} * as this applies some heuristics to get more specific error codes and flattens the message into a single * exception. *

*

* If {@link #isEmpty()} returns {@code false}, then this will throw an {@link IllegalStateException}. *

* * @return SQLException object * @see #toFlatSQLException() */ public SQLException toSQLException() { checkNonEmpty(); var chain = new SQLExceptionChainBuilder(); for (ExceptionInformation info : exceptionInfo) { chain.append(info.toSQLException()); } //noinspection DataFlowIssue return chain.getException(); } /** * Set of uninteresting error codes. *

* This is used by {@link #toFlatSQLException()} to find a more suitable error code. *

*/ private static final Set UNINTERESTING_ERROR_CODES = Set.of(0, isc_dsql_error, isc_dsql_line_col_error, isc_dsql_unknown_pos, isc_sqlerr, isc_dsql_command_err, isc_arith_except, isc_cancelled); /** * SQLState success is linked to some informational error message, we consider those 'not interesting' either. */ private static final String SQLSTATE_SUCCESS = "00000"; /** * Converts the builder to a single SQLException instance with a single exception message. *

* This method attempts to assign the most specific error code and SQL state to the returned exception. *

*

* The cause of the returned exception is set to an instance of {@link org.firebirdsql.jdbc.FBSQLExceptionInfo} * which contains the separate items obtained from the status vector. These items are chained together using * the SQLException chain. *

*

* If {@link #isEmpty()} returns {@code false}, then this will throw an {@link IllegalStateException}. *

* * @return SQLException object * @see org.firebirdsql.jdbc.FBSQLExceptionInfo */ public SQLException toFlatSQLException() { checkNonEmpty(); // We are recording the unflattened state if people need the details var chain = new SQLExceptionChainBuilder(); var fullExceptionMessage = new StringBuilder(); ExceptionInformation interestingExceptionInfo = null; for (ExceptionInformation info : exceptionInfo) { if (interestingExceptionInfo == null && !UNINTERESTING_ERROR_CODES.contains(info.errorCode()) && !SQLSTATE_SUCCESS.equals(info.sqlState())) { interestingExceptionInfo = info; } if (!fullExceptionMessage.isEmpty()) { fullExceptionMessage.append("; "); } info.appendMessage(fullExceptionMessage); chain.append(info.toSQLExceptionInfo()); } final ExceptionInformation firstExceptionInfo = exceptionInfo.get(0); if (interestingExceptionInfo == null) { interestingExceptionInfo = firstExceptionInfo; } interestingExceptionInfo.appendErrorInfoSuffix(fullExceptionMessage); /* If the type of the head of the chain is not Type.EXCEPTION we use that, not the type of the interesting * exception info as the head of the chain has been set explicitly to an expected exception type (e.g. Type.WARNING). */ Type exceptionType = firstExceptionInfo.type != Type.EXCEPTION ? firstExceptionInfo.type : interestingExceptionInfo.type; SQLException exception = exceptionType.createSQLException(fullExceptionMessage.toString(), interestingExceptionInfo.sqlState(), interestingExceptionInfo.errorCode()); exception.initCause(chain.getException()); return stripBuilderStackTraceElements(exception); } private void checkNonEmpty() { if (isEmpty()) { throw new IllegalStateException("No information available to build an SQLException"); } } /** * Converts the builder to the appropriate SQLException instance (optionally with a chain of additional * exceptions) and casts to the specified type T. * * @param type * Class of type T * @param * Expected exception type * @return SQLException of type T * @throws ClassCastException * If the first exception created with this builder is not of the specified type * @see #toSQLException() */ public T toSQLException(Class type) throws ClassCastException { return type.cast(toSQLException()); } /** * Converts the builder to the appropriate SQLException instance and casts to the specified type T. * * @param type * Class of type T * @param * Expected exception type * @return SQLException of type T * @throws ClassCastException * If the first exception created with this builder is not of the specified type * @see #toFlatSQLException() */ @SuppressWarnings("unused") public T toFlatSQLException(Class type) throws ClassCastException { return type.cast(toFlatSQLException()); } /** * @return {@code true} if this builder contains exception information, {@code false} otherwise */ public boolean isEmpty() { return exceptionInfo.isEmpty(); } @Override public String toString() { if (exceptionInfo.isEmpty()) return "empty"; return exceptionInfo.toString(); } /** * Sets the next ExceptionInformation object for the specified type. * * @param type * Type of exception * @param errorCode * The Firebird error code */ private void setNextExceptionInformation(Type type, final int errorCode) { exceptionInfo.add(new ExceptionInformation(upgradeType(type, errorCode), errorCode)); } /** * Checks if a more specific exception type is possible (known and compatible) for the specified error code. * * @param type Requested exception type * @param errorCode Error code * @return Upgrade exception type (e.g. {@code (EXCEPTION, isc_login)} will upgrade to {@code NON_TRANSIENT}) */ private static Type upgradeType(final Type type, final int errorCode) { enum TypeUpgrades { NON_TRANSIENT(isc_wirecrypt_incompatible, isc_miss_wirecrypt, isc_wirecrypt_key, isc_wirecrypt_plugin, jb_cryptNoCryptKeyAvailable, jb_cryptAlgorithmNotAvailable, jb_cryptInvalidKey, isc_login, isc_net_write_err, isc_net_read_err, isc_network_error), TIMEOUT(isc_cfg_stmt_timeout, isc_att_stmt_timeout, isc_req_stmt_timeout); private final int[] errorCodes; TypeUpgrades(int... errorCodes) { Arrays.sort(errorCodes); this.errorCodes = errorCodes; } boolean contains(int errorCode) { return Arrays.binarySearch(errorCodes, errorCode) >= 0; } } if (type == Type.EXCEPTION) { if (TypeUpgrades.NON_TRANSIENT.contains(errorCode)) { return Type.NON_TRANSIENT; } if (TypeUpgrades.TIMEOUT.contains(errorCode)) { return Type.TIMEOUT; } } return type; } /** * Check if we have a current ExceptionInformation object. * * @throws IllegalStateException * If current is null ({@link #warning(int)} or {@link #exception(int)} hasn't been called yet) */ private ExceptionInformation requireExceptionInformation() throws IllegalStateException { ExceptionInformation current = CollectionUtils.getLast(exceptionInfo); if (current == null) { throw new IllegalStateException("FbExceptionBuilder requires call to warning() or exception() first"); } return current; } /** * Removes the {@link StackTraceElement} from this builder class or its nested classes from {@code exception}. * * @param exception * exception to modify * @return same object as {@exception} after modification * @since 6 */ private static SQLException stripBuilderStackTraceElements(SQLException exception) { exception.setStackTrace(stripBuilderStackTraceElements(exception.getStackTrace())); return exception; } /** * Removes the {@link StackTraceElement} from this builder class or its nested classes from * {@code stackTraceElements}. * * @param stackTraceElements * original stacktrace elements * @return new array of {@link StackTraceElement} with the elements of this builder class or its nested classes * removed (original array if there were no such elements, or all elements are from this builder class) * @since 6 */ private static StackTraceElement[] stripBuilderStackTraceElements(StackTraceElement[] stackTraceElements) { int startIndex = findFirstNonBuilderElement(stackTraceElements); // No elements or all elements from this class, return original. // This is unlikely to happen in practice, unless this method is called multiple times on the same exception if (startIndex <= 0) return stackTraceElements; return Arrays.copyOfRange(stackTraceElements, startIndex, stackTraceElements.length); } /** * Finds the first {@link StackTraceElement} that was not produced by this builder class or its nested classes. * * @param stackTraceElements * stacktrace elements to search * @return position of first element that was not produce by this builder class or its nested classes, {@code -1} if * all elements are from this builder class * @since 6 */ private static int findFirstNonBuilderElement(StackTraceElement[] stackTraceElements) { final String thisClassName = FbExceptionBuilder.class.getName(); final String nestedClassPrefix = thisClassName + "$"; for (int idx = 0; idx < stackTraceElements.length; idx++) { String className = stackTraceElements[idx].getClassName(); if (!className.equals(thisClassName) && !className.startsWith(nestedClassPrefix)) { return idx; } } return -1; } private static final class ExceptionInformation { private final Type type; private final List<@Nullable Object> messageParameters = new ArrayList<>(); private MessageTemplate messageTemplate; private @Nullable Throwable cause; ExceptionInformation(Type type, int errorCode) { this.type = requireNonNull(type, "type"); messageTemplate = MessageTemplate.of(errorCode).withDefaultSqlState(type.getDefaultSQLState()); } int errorCode() { return messageTemplate.errorCode(); } @Nullable String sqlState() { return messageTemplate.sqlState(); } /** * Overrides the SQL state. By default, the SQL state is decided by the errorCode. * * @param sqlState * New SQL state value * @throws NullPointerException * if {@code sqlState} is {@code null} * @throws IllegalArgumentException * if {@code sqlState} is not 5-characters long */ void setSqlState(String sqlState) { messageTemplate = messageTemplate.withSqlState(sqlState); } /** * Sets the cause of the exception. * * @param cause * cause of the exception */ void setCause(Throwable cause) { this.cause = cause; } /** * Adds a message parameter. * * @param argument * message parameter */ void addMessageParameter(@Nullable Object argument) { messageParameters.add(argument); } /** * @return message string with the parameters substituted into the message. */ String toMessage() { return messageTemplate.toMessage(messageParameters); } /** * Appends the message, with the parameters substituted, to {@code messageBuffer}. * * @param messageBuffer * string builder to append to */ void appendMessage(StringBuilder messageBuffer) { messageTemplate.appendMessage(messageBuffer, messageParameters); } /** * Appends the SQLstate and error code suffix to {@code messageBuffer}. * * @param messageBuffer * string builder to append to */ void appendErrorInfoSuffix(StringBuilder messageBuffer) { messageTemplate.appendErrorInfoSuffix(messageBuffer); } /** * Converts this ExceptionInformation object into an SQLException * * @return SQLException */ SQLException toSQLException() { // Sizing to 0, as appendMessage will resize, and that will almost always be bigger than the default var messageBuffer = new StringBuilder(0); appendMessage(messageBuffer); appendErrorInfoSuffix(messageBuffer); SQLException result = type.createSQLException(messageBuffer.toString(), sqlState(), errorCode()); if (cause != null) { result.initCause(cause); } return stripBuilderStackTraceElements(result); } FBSQLExceptionInfo toSQLExceptionInfo() { var result = new FBSQLExceptionInfo(toMessage(), sqlState(), errorCode()); if (cause != null) { result.initCause(cause); } return result; } @Override public String toString() { return "Type: " + type + "; ErrorCode: " + errorCode() + "; Message: \"" + toMessage() + '"' + "; SQLstate: " + sqlState() + "; MessageParameters: " + messageParameters + "; Cause: " + cause; } } /** * Caches the rendered message and SQLSTATE of an exception; used for internal optimization purposes. * * @param message * rendered message string * @param sqlState * SQLSTATE * @since 6 */ private record CachedMessage(String message, String sqlState) { /** * Renders the exception using {@code #toSQLException} and stores the resulting message and SQLSTATE. *

* Do not use for parameterized messages. *

* * @param errorCode * Firebird/Jaybird error code * @return cached message with the message and SQLSTATE from the generated exception */ private static CachedMessage of(int errorCode) { SQLException exception = toException(errorCode); return new CachedMessage(exception.getMessage(), exception.getSQLState()); } } /** * Type of exception. */ private enum Type { /** * General {@link SQLException}, the actual type is determined by the builder. */ EXCEPTION(SQLStateConstants.SQL_STATE_GENERAL_ERROR) { @Override public SQLException createSQLException(String message, @Nullable String sqlState, int errorCode) { if (sqlState != null) { if (sqlState.startsWith(SQLSTATE_FEATURE_NOT_SUPPORTED_PREFIX)) { // Feature not supported by Firebird or Jaybird return new SQLFeatureNotSupportedException(message, sqlState, errorCode); } else if (sqlState.startsWith(SQLSTATE_SYNTAX_ERROR_PREFIX)) { return new SQLSyntaxErrorException(message, sqlState, errorCode); } // TODO Add support for other SQLException types } return new SQLException(message, sqlState, errorCode); // TODO If sqlState is 01xxx return SQLWarning any way? } }, /** * Warning, exception created is of {@link SQLWarning} or a subclass */ WARNING(SQLStateConstants.SQL_STATE_WARNING) { @Override public SQLException createSQLException(String message, @Nullable String sqlState, int errorCode) { return new SQLWarning(message, sqlState, errorCode); } }, /** * Force builder to create a {@link java.sql.SQLTimeoutException} or subclass. */ // TODO Specific default sqlstate for timeout? TIMEOUT(SQLStateConstants.SQL_STATE_GENERAL_ERROR) { @Override public SQLException createSQLException(String message, @Nullable String sqlState, int errorCode) { return new SQLTimeoutException(message, sqlState, errorCode); } }, /** * Force builder to create a {@link java.sql.SQLNonTransientException} or subclass. */ NON_TRANSIENT(SQLStateConstants.SQL_STATE_GENERAL_ERROR) { @Override public SQLException createSQLException(String message, @Nullable String sqlState, int errorCode) { // TODO We probably want these specific exception types also for 'normal' exceptions return switch (errorCode) { case isc_wirecrypt_incompatible, isc_miss_wirecrypt, isc_wirecrypt_key, isc_wirecrypt_plugin, jb_cryptNoCryptKeyAvailable, jb_cryptAlgorithmNotAvailable, jb_cryptInvalidKey -> new FBSQLEncryptException(message, sqlState, errorCode); case isc_login -> new SQLInvalidAuthorizationSpecException(message, sqlState, errorCode); default -> { if (sqlState != null) { if (sqlState.startsWith(SQLSTATE_FEATURE_NOT_SUPPORTED_PREFIX)) { // Feature not supported by Firebird or Jaybird yield new SQLFeatureNotSupportedException(message, sqlState, errorCode); } else if (sqlState.startsWith(SQLSTATE_SYNTAX_ERROR_PREFIX)) { yield new SQLSyntaxErrorException(message, sqlState, errorCode); } else if (sqlState.startsWith(SQLSTATE_CONNECTION_ERROR_PREFIX)) { yield new SQLNonTransientConnectionException(message, sqlState, errorCode); } } yield new SQLNonTransientException(message, sqlState, errorCode); } }; } }, /** * Force builder to create a {@link java.sql.SQLNonTransientConnectionException} or a subclass. */ NON_TRANSIENT_CONNECT(SQLStateConstants.SQL_STATE_CONNECTION_ERROR) { @Override public SQLException createSQLException(String message, @Nullable String sqlState, int errorCode) { return new SQLNonTransientConnectionException(message, sqlState, errorCode); } }, /** * Force build to create a {@link java.sql.SQLTransientException} or a subclass. */ TRANSIENT(SQLStateConstants.SQL_STATE_GENERAL_ERROR) { @Override public SQLException createSQLException(String message, @Nullable String sqlState, int errorCode) { return new SQLTransientException(message, sqlState, errorCode); } }; private final String defaultSQLState; Type(String defaultSQLState) { this.defaultSQLState = requireNonNull(defaultSQLState, "defaultSQLState"); } /** * The default SQL State for this type * * @return default SQL State */ public final String getDefaultSQLState() { return defaultSQLState; } /** * Creates an instance SQLException (or a subclass) based on this Type and additional rules based * on errorCode and/or SQLState. * * @param message * message text * @param sqlState * SQL state * @param errorCode * error code * @return instance of SQLException (or a subclass). */ public abstract SQLException createSQLException(String message, @Nullable String sqlState, int errorCode); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy