Home

ExceptionMapper interface

Contents

ExceptionMapper interface

When a user plug-in implementation throws an exception, eXtreme Scale checks certain exceptions defined in the throws contract. However, sometimes an unchecked exception contains a contract exception or the exception does not observe the contract appropriately. Therefore a mechanism is necessary to map the exception to the contract exception if possible, such as ExceptionMapper.


Configure the bean

Consider that a JPALoader must throw a LoaderNotAvailableException when the database server or network is not functional or the database runs out of resources. However, the JPA provider implementation normally just throws a generic PersistenceException with an SQLException, the SQL state or error code of which could indicate the database server problem or network problem. To further complicate the situation, different databases have different SQL state and error codes for such problems. So the exception-mapping mechanism has to be specific to the database.

The ExceptionMapper interface is used to solve the problem. It has a method Throwable map(Throwable original) to map the original exception to a consumable exception.

For example, to solve the stated problem, the implementation class could introspect the SQL state and error code of the java.sql.SQLException chained in the JPA exception. Then, it can throw a LoaderNotAvailableException if the SQL state or error code indicates the database server or network is not functional or the database runs out of resources.

Currently, the ExceptionMapper bean can only be configured in the JPATxCallback ObjectGrid beans. It is used to map all the exceptions received from the JPATxCallback and JPALoader or JPAEntityLoader beans.

To configure an ExceptionMapper, you have to use a Spring-style configuration for the ExceptionMapper bean inside of the JPATxCallback bean.

See Configuring JPA loaders for information about how to use Spring-style configuration for a JPALoader.

The following code is an example in which we map the JPA exceptions to a LoaderNotAvailableException if it indicates a database server problem or network problem. This ExceptionMapper implementation is for using a JPA provider with an MSSQL database. First, define a set of SQL error codes and SQL states that indicate the particular network problem or database server problem. In the map method, first check the SQL error code against a list of known error codes, then the SQL states, and finally the message. If one of them matches, throw a LoaderNotAvaliableException.


Code example

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;
    }