ExceptionMapper インターフェース

ユーザー・プラグイン実装が例外をスローすると、eXtreme Scale は、throws 契約内に定義された特定の例外をチェックします。 しかし、時にはチェックなし例外に契約例外が含まれたり、例外が適切に契約を監視しなかったりすることがあります。そのため、可能であれば、例えば ExceptionMapper など、例外を契約例外にマップするメカニズムが必要になります。

Bean の構成

データベース・サーバーまたはネットワークが作動不可であるときや、データベースがリソースを使い尽くしたときに、JPALoader が LoaderNotAvailableException をスローしなければならない場合を検討します。 しかし、JPA プロバイダー実装は通常、SQL 状態またはエラー・コードがデータベース・サーバー問題またはネットワーク問題を示す可能性のある SQLException を伴う一般 PersistenceException をスローするだけです。 このような問題についてデータベースが異なれば SQL 状態やエラー・コードも異なるということが、状況をさらに複雑にします。 したがって、例外マッピングのメカニズムは、データベースに固有である必要があります。

この問題の解決するために、ExceptionMapper インターフェースが使用されます。このインターフェースには、オリジナルの例外を消費可能な例外にマップする Throwable map(Throwable original) というメソッドがあります。

例えば、上記の問題を解決するために、実装クラスが JPA 例外の中でチェーニングされた java.sql.SQLException の SQL 状態とエラー・コードをイントロスペクトすることもできます。 このようにすると、SQL 状態またはエラー・コードが、データベース・サーバーまたはネットワークが作動不可であったり、データベースがリソースを使い尽くしたりしたことを示した場合に、実装クラスが LoaderNotAvailableException をスローできます。

現在、ExceptionMapper Bean は、JPATxCallback ObjectGrid Bean の中にのみ構成できます。ExceptionMapper Bean は、JPATxCallback から受け取ったすべての例外を、JPALoader Bean または JPAEntityLoader Bean にマップするために使用します。

ExceptionMapper を構成するには、JPATxCallback Bean 内の ExceptionMapper Bean に対して、Spring スタイルの構成を使用する必要があります。

JPALoader に Spring スタイルの構成を使用する方法については、JPA ローダーの構成を参照してください。

次の例では、データベース・サーバー問題やネットワーク問題が示された場合に、JPA 例外を LoaderNotAvailableException にマップしています。この ExceptionMapper 実装は、JPA プロバイダーを MSSQL データベースと一緒に使用するためのものです。 まず、特定のネットワーク問題またはデータベース・サーバー問題を示す、一連の SQL エラー・コードと SQL 状態を定義します。マップ・メソッドの中で、SQL エラー・コードを、最初に既知のエラー・コードのリストに対して、次に SQL 状態に対して、最後にメッセージに対してチェックします。そのうちのいずれか 1 つが一致すれば、LoaderNotAvaliableException をスローします。

コード例

public class JPAMSSQLExceptionMapper implements ExceptionMapper {

    static Set<Integer> loaderNotAvailableSQLErrorSet = new HashSet<Integer>();

    static Set<String> loaderNotAvailableSQLStateSet = new HashSet<String>();

    static {
        addInitialMaps();
    }

    public C3P0MSSQLExceptionMapper() {
        System.out.println("C3P0MSSQLExceptionMapper is constructed");
    }

    /**
     * @internal
     * This method is used to add intial maps to the hash, this is used
     * internally, and it is not for public view
     */
    private static void addInitialMaps() {
        //  http://msdn.microsoft.com/en-us/library/cc645603.aspx
        loaderNotAvailableSQLErrorSet.add(new Integer(230));
        loaderNotAvailableSQLErrorSet.add(new Integer(6002));

        // http://white-box.us/2009/03/08/sql-92-sqlstate-codes/
        /*
         * 08001 SQL client unable to establish SQL connection 
         * 08002 connection name in use 
         * 08003 connection does not exist 
         * 08004 SQL server rejected SQL connection 
         * 08006 connection failure 
         * 08007 transaction resolution unknown 
         */
        loaderNotAvailableSQLStateSet.add("08000");
        loaderNotAvailableSQLStateSet.add("08001");
        loaderNotAvailableSQLStateSet.add("08002");
        loaderNotAvailableSQLStateSet.add("08003");
        loaderNotAvailableSQLStateSet.add("08004");
        loaderNotAvailableSQLStateSet.add("08006");
        loaderNotAvailableSQLStateSet.add("08007");

        // http://msdn.microsoft.com/en-us/library/ms714687.aspx
        loaderNotAvailableSQLStateSet.add("08S01");
        loaderNotAvailableSQLStateSet.add("HY000");
    }

    private static Pattern[] sqlServerMessagePatterns = new Pattern[] {
            Pattern.compile("The TCP/IP connection to the host .* has failed.*"), Pattern.compile(".*Connection reset.*") };

    private static Pattern[] sqlExceptionMessagePatterns = new Pattern[] { Pattern
            .compile(".*Connections could not be acquired from the underlying database.*") };

    private static String connection_reset = "Connection reset";

    public Throwable map(Throwable originalEx) {

        Throwable cause = originalEx;

        while (cause != null) {
            // keep looping to check the next chained exception
            if (cause instanceof SQLException) {
                // Only check if the exception is an SQLException

                SQLException sqle = (SQLException) cause;

                // If the loader not available SQL state set contains this SQL state, then
                // we return a LoaderNotAvailableException with the original exception chained in it.
                if (loaderNotAvailableSQLStateSet.contains(sqle.getSQLState())) {
                    return new LoaderNotAvailableException(originalEx);
                }

                // If the loader not available SQL error code set contains this error code, then
                // we return a LoaderNotAvailableException with the original exception chained in it
                if (loaderNotAvailableSQLErrorSet.contains(new Integer(sqle.getErrorCode()))) {
                    return new LoaderNotAvailableException(originalEx);
                }
                
                String msg = sqle.getMessage();
                for (int i=0; i<sqlExceptionMessagePatterns.length; i++) {
                    if (sqlExceptionMessagePatterns[i].matcher(msg).matches()) {
                        return new LoaderNotAvailableException(originalEx);
                    }
                }

            } else if (cause.getClass().getName().equals("com.microsoft.sqlserver.jdbc.SQLServerException")) {
                String msg = cause.getMessage();
                for (int i=0; i<sqlServerMessagePatterns.length; i++) {
                    if (sqlServerMessagePatterns[i].matcher(msg).matches()) {
                        return new LoaderNotAvailableException(originalEx);
                    }
                }
                System.out.println("msg " + msg + " does not match");
            }

            // Get the next chained exception
            Throwable newCause = cause.getCause();

            // Safe-guard check to avoid indefinite loop if the exception chains itself
            if (newCause == cause) {
                // Always return the original exception if cannot map it.
                return originalEx;
            } else {
                cause = newCause;
            }
        }

        // Always retrun the original exception if cannot map it.
        return originalEx;
    }