killbill-aplcache

util: even nicer error message in EntitySqlDaoWrapperInvocationHandler This

11/9/2012 6:04:16 PM

Details

diff --git a/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDaoWrapperFactory.java b/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDaoWrapperFactory.java
index 03fdf7f..bd4ea2c 100644
--- a/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDaoWrapperFactory.java
+++ b/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDaoWrapperFactory.java
@@ -56,7 +56,7 @@ public class EntitySqlDaoWrapperFactory<InitialSqlDao extends EntitySqlDao> {
     private <NewSqlDao extends EntitySqlDao<NewEntity>, NewEntity extends Entity> NewSqlDao create(final Class<NewSqlDao> newSqlDaoClass, final NewSqlDao newSqlDao) {
         final ClassLoader classLoader = newSqlDao.getClass().getClassLoader();
         final Class[] interfacesToImplement = {newSqlDaoClass};
-        final EntitySqlDaoWrapperInvocationHandler<NewSqlDao, NewEntity> wrapperInvocationHandler = new EntitySqlDaoWrapperInvocationHandler<NewSqlDao, NewEntity>(newSqlDao);
+        final EntitySqlDaoWrapperInvocationHandler<NewSqlDao, NewEntity> wrapperInvocationHandler = new EntitySqlDaoWrapperInvocationHandler<NewSqlDao, NewEntity>(newSqlDaoClass, newSqlDao);
 
         final Object newSqlDaoObject = Proxy.newProxyInstance(classLoader, interfacesToImplement, wrapperInvocationHandler);
         return newSqlDaoClass.cast(newSqlDaoObject);
diff --git a/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java b/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java
index eda110f..fbf6a1c 100644
--- a/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java
+++ b/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java
@@ -19,12 +19,16 @@ package com.ning.billing.util.entity.dao;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
+import org.skife.jdbi.v2.StatementContext;
 import org.skife.jdbi.v2.exceptions.DBIException;
+import org.skife.jdbi.v2.exceptions.StatementException;
 import org.skife.jdbi.v2.sqlobject.Bind;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -49,9 +53,11 @@ public class EntitySqlDaoWrapperInvocationHandler<T extends EntitySqlDao<U>, U e
 
     private final Logger logger = LoggerFactory.getLogger(EntitySqlDaoWrapperInvocationHandler.class);
 
+    private final Class<T> sqlDaoClass;
     private final T sqlDao;
 
-    public EntitySqlDaoWrapperInvocationHandler(final T sqlDao) {
+    public EntitySqlDaoWrapperInvocationHandler(final Class<T> sqlDaoClass, final T sqlDao) {
+        this.sqlDaoClass = sqlDaoClass;
         this.sqlDao = sqlDao;
     }
 
@@ -60,16 +66,25 @@ public class EntitySqlDaoWrapperInvocationHandler<T extends EntitySqlDao<U>, U e
         try {
             return invokeSafely(proxy, method, args);
         } catch (Throwable t) {
-            logger.warn("Error during transaction for entity {} and method {}", sqlDao.getClass(), method.getName());
-
             if (t.getCause() != null && t.getCause().getCause() != null && DBIException.class.isAssignableFrom(t.getCause().getClass())) {
                 // Likely the JDBC exception or a Billing exception we have thrown in the transaction
-                errorDuringTransaction(t.getCause().getCause());
+                // If it's a JDBC error, try to extract the SQL statement
+                if (t.getCause() instanceof StatementException) {
+                    final StatementContext statementContext = ((StatementException) t.getCause()).getStatementContext();
+                    if (statementContext != null) {
+                        final PreparedStatement statement = statementContext.getStatement();
+                        // Note: we rely on the JDBC driver to have a sane toString() method...
+                        errorDuringTransaction(t.getCause().getCause(), method, statement.toString());
+                        // Never reached
+                        return null;
+                    }
+                }
+                errorDuringTransaction(t.getCause().getCause(), method);
             } else if (t.getCause() != null) {
                 // t is likely not interesting (java.lang.reflect.InvocationTargetException)
-                errorDuringTransaction(t.getCause());
+                errorDuringTransaction(t.getCause(), method);
             } else {
-                errorDuringTransaction(t);
+                errorDuringTransaction(t, method);
             }
         }
 
@@ -78,7 +93,22 @@ public class EntitySqlDaoWrapperInvocationHandler<T extends EntitySqlDao<U>, U e
     }
 
     // Nice method name to ease debugging while looking at log files
-    private void errorDuringTransaction(final Throwable t) throws Throwable {
+    private void errorDuringTransaction(final Throwable t, final Method method, final String extraErrorMessage) throws Throwable {
+        final StringBuilder errorMessageBuilder = new StringBuilder("Error during transaction for sql entity {} and method {}");
+        if (t instanceof SQLException) {
+            final SQLException sqlException = (SQLException) t;
+            errorMessageBuilder.append(" [SQL State: ")
+                               .append(sqlException.getSQLState())
+                               .append(", Vendor Error Code: ")
+                               .append(sqlException.getErrorCode())
+                               .append("]");
+        }
+        if (extraErrorMessage != null) {
+            // This is usually the SQL statement
+            errorMessageBuilder.append("\n").append(extraErrorMessage);
+        }
+        logger.warn(errorMessageBuilder.toString(), sqlDaoClass, method.getName());
+
         // This is to avoid throwing an exception wrapped in an UndeclaredThrowableException
         if (!(t instanceof RuntimeException)) {
             throw new RuntimeException(t);
@@ -87,6 +117,10 @@ public class EntitySqlDaoWrapperInvocationHandler<T extends EntitySqlDao<U>, U e
         }
     }
 
+    private void errorDuringTransaction(final Throwable t, final Method method) throws Throwable {
+        errorDuringTransaction(t, method, null);
+    }
+
     private Object invokeSafely(final Object proxy, final Method method, final Object[] args) throws Throwable {
         final Audited annotation = method.getAnnotation(Audited.class);