Schnittstelle "ExceptionMapper"

Wenn eine Benutzer-Plug-in-Implementierung eine Ausnahme auslöst, überprüft eXtreme Scale bestimmte Ausnahmen, die im throws-Vertrag definiert sind. Manchmal enthält eine ungeprüfte Ausnahme jedoch eine Vertragsausnahme, oder die Ausnahme beobachtet den Vertrag nicht ordnungsgemäß. Deshalb ist ein Mechanismus erforderlich, der die Ausnahme, sofern möglich, der Vertragsausnahme, z. B. ExceptionMapper, zuordnet.

Bean konfigurieren

Angenommen, ein JPALoader muss eine Ausnahme des Typs "LoaderNotAvailableException" auslösen, wenn der Datenbankserver oder das Netz nicht funktionsbereit ist oder wenn die Ressourcenkapazität der Datenbank erschöpft ist. Die JPA-Providerimplementierung löst jedoch normalerweise nur eine generische Ausnahme des Typs "PersistenceException" mit einer Ausnahme des Typs "SQLException" aus, deren SQL-Status oder Fehlercode das Problem des Datenbankservers oder Netzproblems anzeigt. Es die Situation noch weiter kompliziert ist die Tatsache, dass verschiedene Datenbanken verschiedene SQL-Status und Fehlercodes für solche Probleme verwenden. Deshalb muss der Mechanismus für die Ausnahmezuordnung für die Datenbank spezifisch sein.

Zur Behebung des Problems wird die Schnittstelle ExceptionMapper verwendet. Diese Schnittstelle hat eine Methode mit dem Namen Throwable map(Throwable original), die die ursprüngliche Ausnahme einer Hilfsausnahme zuordnet.

Um das gemeldete Problem zu beheben, kann die Implementierungsklasse beispielsweise den SQL-Status und den Fehlercode der Ausname "java.sql.SQLException", die in der JPA-Ausnahme verkettet ist, selbst überwachen. Anschließend kann sie eine Ausnahme des Typs LoaderNotAvailableException auslösen wenn der SQL-Status oder Fehlercode darauf hinweist, dass der Datenbankserver oder das Netz nicht funktionsbereit oder die Ressourcenkapazität der Datenbank erschöpft ist.

Momentan kann die Bean "ExceptionMapper" nur in den JPATxCallback-ObjectGrid-Beans konfiguriert werden. Sie wird verwendet, um alle Ausnahmen zuzuordnen, die von JPATxCallback- und JPALoader- oder JPAEntityLoader-Beans empfangen werden.

Zum Konfigurieren einer ExceptionMapper-Bean müssen Sie eine Konfiguration im Spring-Stil für die ExceptionMapper-Bean in der JPATxCallback-Bean verwenden.

Informationen zur Verwendung einer Konfiguration im Spring-Stil für einen JPALoader finden Sie unter JPA-Loader konfigurieren.

Im Folgenden sehen Sie ein Beispiel, in dem die JPA-Ausnahmen einer Ausnahme "LoaderNotAvailableException" zugeordnet werden, wenn auf ein Problem mit dem Datenbankserver oder ein Netzproblem hingewiesen wird. Diese ExceptionMapper-Implementierung ist für die Verwendung eines JPA-Providers mit einer MSSQL-Datenbank bestimmt. Definieren Sie zuerst eine Gruppe von SQL-Fehlercodes und SQL-Status, die das jeweilige Netzproblem oder Problem des Datenbankservers angeben. Überprüfen Sie in der Zuordnungsmethode (map) zuerst den SQL-Fehlercode anhand einer Liste bekannter Fehlercodes, dann die SQL-Status und schließlich die Nachricht. Lösen Sie eine Ausnahme des Typs "LoaderNotAvaliableException" aus, wenn eine Übereinstimmung gefunden wird.

Codebeispiel

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
     * Diese Methode wird verwendet, um dem Hash, der intern verwendet wird, erste
     * Maps hinzuzufügen. Der Hash ist nicht für eine öffentliche Ansicht bestimmt.
     */
    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 kann keine SQL-Verbindung herstellen
         * 08002 Verwendeter Verbindungsname
         * 08003 Verbindung ist nicht vorhanden
         * 08004 SQL-Server weist SQL-Verbindung zurück
         * 08006 Verbindungsfehler
         * 08007 Transaktionsauflösung unbekannt
         */
        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) {
            // Schleife beibehalten, um nächste verkettete Ausnahmebedingung zu prüfen
            if (cause instanceof SQLException) {
                // Nur prüfen, ob die Ausnahme eine SQLException ist

                SQLException sqle = (SQLException) cause;

                // Wenn die Gruppe der dem Loader nicht verfügbaren SQL-Status diese
                // SQL-Ausnahme enthält, LoaderNotAvailableException mit ursprünglicher
                // Ausnahme zurückgeben, die darin verkettet ist.
                if (loaderNotAvailableSQLStateSet.contains(sqle.getSQLState())) {
                    return new LoaderNotAvailableException(originalEx);
                }

                // Wenn die Gruppe der dem Loader nicht verfügbaren SQL-Fehlercodes diesen
                // Fehlercode enthält, LoaderNotAvailableException mit ursprünglicher
                // Ausnahme zurückgeben, die darin verkettet ist.
                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");
            }

            // Nächste verkettete Ausnahme abrufen
            Throwable newCause = cause.getCause();

            // Sicherheitsprüfung, um Endlosschleife zu verhindern, wenn
            // die Ausnahme mit sich selbst verkettet wird
            if (newCause == cause) {
                // Immer ursprüngliche Ausnahme zurückgeben, wenn eine
                // Zuordnung nicht möglich ist.
                return originalEx;
            } else {
                cause = newCause;
            }
        }

        // Immer ursprüngliche Ausnahme zurückgeben, wenn eine
        // Zuordnung nicht möglich ist.
        return originalEx;
    }