killbill-aplcache

Merge branch 'audit-rework' of github.com:killbill/killbill

11/9/2012 1:24:53 AM

Changes

Details

diff --git a/account/src/main/java/com/ning/billing/account/api/svcs/DefaultAccountInternalApi.java b/account/src/main/java/com/ning/billing/account/api/svcs/DefaultAccountInternalApi.java
index 8de582c..7a84076 100644
--- a/account/src/main/java/com/ning/billing/account/api/svcs/DefaultAccountInternalApi.java
+++ b/account/src/main/java/com/ning/billing/account/api/svcs/DefaultAccountInternalApi.java
@@ -76,7 +76,7 @@ public class DefaultAccountInternalApi implements AccountInternalApi {
     @Override
     public List<AccountEmail> getEmails(final UUID accountId,
                                         final InternalTenantContext context) {
-        return accountEmailDao.getEmails(accountId, context);
+        return accountEmailDao.getByAccountId(accountId, context);
     }
 
     @Override
diff --git a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
index 31eec47..9869b97 100644
--- a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
+++ b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
@@ -151,16 +151,16 @@ public class DefaultAccountUserApi implements AccountUserApi {
 
     @Override
     public List<AccountEmail> getEmails(final UUID accountId, final TenantContext context) {
-        return accountEmailDao.getEmails(accountId, internalCallContextFactory.createInternalTenantContext(context));
+        return accountEmailDao.getByAccountId(accountId, internalCallContextFactory.createInternalTenantContext(context));
     }
 
     @Override
-    public void addEmail(final UUID accountId, final AccountEmail email, final CallContext context) {
-        accountEmailDao.addEmail(accountId, email, internalCallContextFactory.createInternalCallContext(accountId, context));
+    public void addEmail(final UUID accountId, final AccountEmail email, final CallContext context) throws AccountApiException {
+        accountEmailDao.create(email, internalCallContextFactory.createInternalCallContext(accountId, context));
     }
 
     @Override
     public void removeEmail(final UUID accountId, final AccountEmail email, final CallContext context) {
-        accountEmailDao.removeEmail(accountId, email, internalCallContextFactory.createInternalCallContext(accountId, context));
+        accountEmailDao.delete(email, internalCallContextFactory.createInternalCallContext(accountId, context));
     }
 }
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountEmailDao.java b/account/src/main/java/com/ning/billing/account/dao/AccountEmailDao.java
index 5026e62..c7abebd 100644
--- a/account/src/main/java/com/ning/billing/account/dao/AccountEmailDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountEmailDao.java
@@ -27,18 +27,7 @@ import com.ning.billing.util.entity.dao.EntityDao;
 
 public interface AccountEmailDao extends EntityDao<AccountEmail, AccountApiException> {
 
-    public List<AccountEmail> getEmails(UUID accountId, InternalTenantContext context);
+    public void delete(AccountEmail email, InternalCallContext context);
 
-    /**
-     * Add, remove or update the list of emails in this account.
-     *
-     * @param accountId id of the account
-     * @param emails    the final list of emails
-     * @param context   the call context
-     */
-    public void saveEmails(UUID accountId, List<AccountEmail> emails, InternalCallContext context);
-
-    public void addEmail(UUID accountId, AccountEmail email, InternalCallContext context);
-
-    public void removeEmail(UUID accountId, AccountEmail email, InternalCallContext context);
+    public List<AccountEmail> getByAccountId(UUID accountId, InternalTenantContext context);
 }
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountEmailSqlDao.java b/account/src/main/java/com/ning/billing/account/dao/AccountEmailSqlDao.java
index ab569d8..7beb12f 100644
--- a/account/src/main/java/com/ning/billing/account/dao/AccountEmailSqlDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountEmailSqlDao.java
@@ -16,52 +16,30 @@
 
 package com.ning.billing.account.dao;
 
-import java.util.Collection;
 import java.util.List;
+import java.util.UUID;
 
 import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.SqlBatch;
+import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
 import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
-import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
 
-import com.ning.billing.ObjectType;
 import com.ning.billing.account.api.AccountEmail;
 import com.ning.billing.util.callcontext.InternalCallContext;
-import com.ning.billing.util.callcontext.InternalTenantContextBinder;
-import com.ning.billing.util.dao.EntityHistory;
-import com.ning.billing.util.dao.ObjectTypeBinder;
-import com.ning.billing.util.entity.collection.dao.UpdatableEntityCollectionSqlDao;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.entity.dao.EntitySqlDao;
 import com.ning.billing.util.entity.dao.EntitySqlDaoStringTemplate;
 
 @EntitySqlDaoStringTemplate
 @RegisterMapper(AccountEmailMapper.class)
-public interface AccountEmailSqlDao extends UpdatableEntityCollectionSqlDao<AccountEmail>, Transactional<AccountEmailSqlDao> {
+public interface AccountEmailSqlDao extends EntitySqlDao<AccountEmail> {
 
-    @Override
-    @SqlBatch
-    public void insertFromTransaction(@Bind("objectId") final String objectId,
-                                      @ObjectTypeBinder final ObjectType objectType,
-                                      @AccountEmailBinder final Collection<AccountEmail> entities,
-                                      @InternalTenantContextBinder final InternalCallContext context);
+    @SqlUpdate
+    public void delete(@BindBean final AccountEmail accountEmail,
+                       @BindBean final InternalCallContext context);
 
-    @Override
-    @SqlBatch
-    public void updateFromTransaction(@Bind("objectId") final String objectId,
-                                      @ObjectTypeBinder final ObjectType objectType,
-                                      @AccountEmailBinder final Collection<AccountEmail> entities,
-                                      @InternalTenantContextBinder final InternalCallContext context);
-
-    @Override
-    @SqlBatch
-    public void deleteFromTransaction(@Bind("objectId") final String objectId,
-                                      @ObjectTypeBinder final ObjectType objectType,
-                                      @AccountEmailBinder final Collection<AccountEmail> entities,
-                                      @InternalTenantContextBinder final InternalCallContext context);
-
-    @Override
-    @SqlBatch
-    public void addHistoryFromTransaction(@Bind("objectId") final String objectId,
-                                          @ObjectTypeBinder final ObjectType objectType,
-                                          @AccountEmailHistoryBinder final List<EntityHistory<AccountEmail>> entities,
-                                          @InternalTenantContextBinder final InternalCallContext context);
+    @SqlQuery
+    public List<AccountEmail> getEmailByAccountId(@Bind("accountId") final UUID accountId,
+                                                  @BindBean final InternalTenantContext context);
 }
diff --git a/account/src/main/java/com/ning/billing/account/dao/AuditedAccountEmailDao.java b/account/src/main/java/com/ning/billing/account/dao/AuditedAccountEmailDao.java
index d3745b8..c0a911b 100644
--- a/account/src/main/java/com/ning/billing/account/dao/AuditedAccountEmailDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/AuditedAccountEmailDao.java
@@ -16,152 +16,54 @@
 
 package com.ning.billing.account.dao;
 
-import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.UUID;
 
 import org.skife.jdbi.v2.IDBI;
-import org.skife.jdbi.v2.Transaction;
-import org.skife.jdbi.v2.TransactionStatus;
-import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 
-import com.ning.billing.ObjectType;
+import com.ning.billing.ErrorCode;
 import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.account.api.AccountEmail;
-import com.ning.billing.account.api.DefaultAccountEmail;
 import com.ning.billing.util.callcontext.InternalCallContext;
 import com.ning.billing.util.callcontext.InternalTenantContext;
-import com.ning.billing.util.clock.Clock;
-import com.ning.billing.util.dao.AuditedCollectionDaoBase;
-import com.ning.billing.util.dao.TableName;
-import com.ning.billing.util.entity.EntityPersistenceException;
-import com.ning.billing.util.entity.collection.dao.UpdatableEntityCollectionSqlDao;
+import com.ning.billing.util.entity.dao.EntityDaoBase;
+import com.ning.billing.util.entity.dao.EntitySqlDao;
+import com.ning.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
+import com.ning.billing.util.entity.dao.EntitySqlDaoTransactionalJdbiWrapper;
+import com.ning.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
 
-import com.google.common.collect.ImmutableList;
 import com.google.inject.Inject;
 
-public class AuditedAccountEmailDao extends AuditedCollectionDaoBase<AccountEmail, AccountEmail> implements AccountEmailDao {
-
-    private final AccountEmailSqlDao accountEmailSqlDao;
+public class AuditedAccountEmailDao extends EntityDaoBase<AccountEmail, AccountApiException> implements AccountEmailDao {
 
     @Inject
-    public AuditedAccountEmailDao(final IDBI dbi, final Clock clock) {
-        super(clock);
-        this.accountEmailSqlDao = dbi.onDemand(AccountEmailSqlDao.class);
-    }
-
-    @Override
-    public void create(final AccountEmail entity, final InternalCallContext context) throws AccountApiException {
-        saveEmails(entity.getAccountId(), ImmutableList.<AccountEmail>of(entity), context);
-    }
-
-    @Override
-    public Long getRecordId(final UUID id, final InternalTenantContext context) {
-        return accountEmailSqlDao.getRecordId(id.toString(), context);
-    }
-
-    @Override
-    public AccountEmail getById(final UUID id, final InternalTenantContext context) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public AccountEmail getByRecordId(final Long recordId, final InternalTenantContext context) {
-        throw new UnsupportedOperationException();
-    }
-
-
-    @Override
-    public List<AccountEmail> get(final InternalTenantContext context) {
-        throw new UnsupportedOperationException();
+    public AuditedAccountEmailDao(final IDBI dbi) {
+        super(new EntitySqlDaoTransactionalJdbiWrapper(dbi), AccountEmailSqlDao.class);
     }
 
     @Override
-    protected AccountEmail getEquivalenceObjectFor(final AccountEmail obj) {
-        return obj;
-    }
-
-    @Override
-    public List<AccountEmail> getEmails(final UUID accountId, final InternalTenantContext context) {
-        return new ArrayList<AccountEmail>(super.loadEntities(accountId, ObjectType.ACCOUNT_EMAIL, context).values());
-    }
-
-    @Override
-    public void saveEmails(final UUID accountId, final List<AccountEmail> emails, final InternalCallContext context) {
-        super.saveEntities(accountId, ObjectType.ACCOUNT_EMAIL, emails, context);
-    }
-
-    @Override
-    public void addEmail(final UUID accountId, final AccountEmail email, final InternalCallContext context) {
-        accountEmailSqlDao.inTransaction(new Transaction<Object, AccountEmailSqlDao>() {
+    public void delete(final AccountEmail email, final InternalCallContext context) {
+        transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
             @Override
-            public Object inTransaction(final AccountEmailSqlDao transactional, final TransactionStatus status) throws Exception {
-                // Compute the final list of emails by looking up the current ones and adding the new one
-                // We can use a simple set here as the supplied email may not have its id field populated
-                final List<AccountEmail> currentEmails = accountEmailSqlDao.load(accountId.toString(), ObjectType.ACCOUNT_EMAIL, context);
-                final Map<String, AccountEmail> newEmails = new HashMap<String, AccountEmail>();
-                for (final AccountEmail currentEmail : currentEmails) {
-                    newEmails.put(currentEmail.getEmail(), currentEmail);
-                }
-                // TODO Note the hack here... saveEntitiesFromTransaction() works by comparing objects in sets, so we
-                // have to use DefaultAccountEmail instances (email is likely to be an inner class from AccountEmailJson
-                // here and will confuse AuditedCollectionDaoBase).
-                newEmails.put(email.getEmail(), new DefaultAccountEmail(email.getAccountId(), email.getEmail()));
-
-                saveEntitiesFromTransaction(getSqlDao(context), accountId, ObjectType.ACCOUNT_EMAIL,
-                                            ImmutableList.<AccountEmail>copyOf(newEmails.values()), context);
-
+            public Void inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                entitySqlDaoWrapperFactory.become(AccountEmailSqlDao.class).delete(email, context);
                 return null;
             }
         });
     }
 
     @Override
-    public void removeEmail(final UUID accountId, final AccountEmail email, final InternalCallContext context) {
-        accountEmailSqlDao.inTransaction(new Transaction<Object, AccountEmailSqlDao>() {
+    public List<AccountEmail> getByAccountId(final UUID accountId, final InternalTenantContext context) {
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<AccountEmail>>() {
             @Override
-            public Object inTransaction(final AccountEmailSqlDao transactional, final TransactionStatus status) throws Exception {
-                // Compute the final list of emails by looking up the current ones and removing the new one
-                // We can use a simple set here as the supplied email may not have its id field populated
-                final List<AccountEmail> currentEmails = accountEmailSqlDao.load(accountId.toString(), ObjectType.ACCOUNT_EMAIL, context);
-                final Map<String, AccountEmail> newEmails = new HashMap<String, AccountEmail>();
-                for (final AccountEmail currentEmail : currentEmails) {
-                    newEmails.put(currentEmail.getEmail(), currentEmail);
-                }
-                newEmails.remove(email.getEmail());
-
-                saveEntitiesFromTransaction(getSqlDao(context), accountId, ObjectType.ACCOUNT_EMAIL,
-                                            ImmutableList.<AccountEmail>copyOf(newEmails.values()), context);
-
-                return null;
+            public List<AccountEmail> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                return entitySqlDaoWrapperFactory.become(AccountEmailSqlDao.class).getEmailByAccountId(accountId, context);
             }
         });
     }
 
     @Override
-    public String getKey(final AccountEmail entity, final InternalTenantContext context) {
-        return entity.getEmail();
-    }
-
-    @Override
-    public void test(final InternalTenantContext context) {
-        accountEmailSqlDao.test(context);
-    }
-
-    @Override
-    protected TableName getTableName(final InternalTenantContext context) {
-        return TableName.ACCOUNT_EMAIL_HISTORY;
-    }
-
-    @Override
-    protected UpdatableEntityCollectionSqlDao<AccountEmail> transmogrifyDao(final Transmogrifier transactionalDao, final InternalTenantContext context) {
-        return transactionalDao.become(AccountEmailSqlDao.class);
-    }
-
-    @Override
-    protected UpdatableEntityCollectionSqlDao<AccountEmail> getSqlDao(final InternalTenantContext context) {
-        return accountEmailSqlDao;
+    protected AccountApiException generateAlreadyExistsException(final AccountEmail entity, final InternalCallContext context) {
+        return new AccountApiException(ErrorCode.ACCOUNT_EMAIL_ALREADY_EXISTS, entity.getId());
     }
 }
diff --git a/account/src/main/resources/com/ning/billing/account/dao/AccountEmailSqlDao.sql.stg b/account/src/main/resources/com/ning/billing/account/dao/AccountEmailSqlDao.sql.stg
index fc11f5c..aab878c 100644
--- a/account/src/main/resources/com/ning/billing/account/dao/AccountEmailSqlDao.sql.stg
+++ b/account/src/main/resources/com/ning/billing/account/dao/AccountEmailSqlDao.sql.stg
@@ -20,7 +20,16 @@ tableValues() ::= <<
 , :updatedDate
  >>
 
- historyTableName() ::= "account_email_history"
+historyTableName() ::= "account_email_history"
+
+getEmailByAccountId() ::= <<
+select
+<allTableFields()>
+from <tableName()>
+where account_id = :accountId
+<AND_CHECK_TENANT()>
+;
+>>
 
 updateFromTransaction() ::= <<
     UPDATE account_emails
diff --git a/account/src/main/resources/com/ning/billing/account/ddl.sql b/account/src/main/resources/com/ning/billing/account/ddl.sql
index 47f2138..ef5a2ba 100644
--- a/account/src/main/resources/com/ning/billing/account/ddl.sql
+++ b/account/src/main/resources/com/ning/billing/account/ddl.sql
@@ -35,9 +35,9 @@ CREATE INDEX accounts_tenant_record_id ON accounts(tenant_record_id);
 
 DROP TABLE IF EXISTS account_history;
 CREATE TABLE account_history (
-    history_record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
-    record_id int(11) unsigned NOT NULL,
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
     id char(36) NOT NULL,
+    target_record_id int(11) unsigned NOT NULL,
     external_key varchar(128) NULL,
     email varchar(128) NOT NULL,
     name varchar(100) NOT NULL,
@@ -59,12 +59,14 @@ CREATE TABLE account_history (
     migrated bool DEFAULT false,
     is_notified_for_invoices boolean NOT NULL,
     change_type char(6) NOT NULL,
+    created_by varchar(50) NOT NULL,
+    created_date datetime NOT NULL,
     updated_by varchar(50) NOT NULL,
-    date datetime NOT NULL,
+    updated_date datetime NOT NULL,
     tenant_record_id int(11) unsigned default null,
-    PRIMARY KEY(history_record_id)
-) ENGINE=innodb;
-CREATE INDEX account_history_record_id ON account_history(record_id);
+    PRIMARY KEY(record_id)
+    ) ENGINE=innodb;
+CREATE INDEX account_history_target_record_id ON account_history(target_record_id);
 CREATE INDEX account_history_tenant_record_id ON account_history(tenant_record_id);
 
 DROP TABLE IF EXISTS account_emails;
@@ -87,17 +89,19 @@ CREATE INDEX account_emails_tenant_account_record_id ON account_emails(tenant_re
 
 DROP TABLE IF EXISTS account_email_history;
 CREATE TABLE account_email_history (
-    history_record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
-    record_id int(11) unsigned NOT NULL,
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
     id char(36) NOT NULL,
+    target_record_id int(11) unsigned NOT NULL,
     account_id char(36) NOT NULL,
     email varchar(128) NOT NULL,
     change_type char(6) NOT NULL,
+    created_by varchar(50) NOT NULL,
+    created_date datetime NOT NULL,
     updated_by varchar(50) NOT NULL,
-    date datetime NOT NULL,
+    updated_date datetime NOT NULL,
     account_record_id int(11) unsigned default null,
     tenant_record_id int(11) unsigned default null,
-    PRIMARY KEY(history_record_id)
+    PRIMARY KEY(record_id)
 ) ENGINE=innodb;
-CREATE INDEX account_email_record_id ON account_email_history(record_id);
+CREATE INDEX account_email_target_record_id ON account_email_history(target_record_id);
 CREATE INDEX account_email_history_tenant_account_record_id ON account_email_history(tenant_record_id, account_record_id);
diff --git a/account/src/test/java/com/ning/billing/account/api/user/TestDefaultAccountUserApi.java b/account/src/test/java/com/ning/billing/account/api/user/TestDefaultAccountUserApi.java
index 01284ca..51111aa 100644
--- a/account/src/test/java/com/ning/billing/account/api/user/TestDefaultAccountUserApi.java
+++ b/account/src/test/java/com/ning/billing/account/api/user/TestDefaultAccountUserApi.java
@@ -115,20 +115,20 @@ public class TestDefaultAccountUserApi extends AccountTestSuite {
         final UUID accountId = UUID.randomUUID();
 
         // Verify the initial state
-        Assert.assertEquals(accountEmailDao.getEmails(accountId, tenantContext).size(), 0);
+        Assert.assertEquals(accountEmailDao.getByAccountId(accountId, tenantContext).size(), 0);
 
         // Add the first email
         final String email1 = UUID.randomUUID().toString();
         accountUserApi.addEmail(accountId, new DefaultAccountEmail(accountId, email1), callContext);
-        Assert.assertEquals(accountEmailDao.getEmails(accountId, tenantContext).size(), 1);
+        Assert.assertEquals(accountEmailDao.getByAccountId(accountId, tenantContext).size(), 1);
 
         // Add a second one
         final String email2 = UUID.randomUUID().toString();
         accountUserApi.addEmail(accountId, new DefaultAccountEmail(accountId, email2), callContext);
-        Assert.assertEquals(accountEmailDao.getEmails(accountId, tenantContext).size(), 2);
+        Assert.assertEquals(accountEmailDao.getByAccountId(accountId, tenantContext).size(), 2);
 
         // Remove the first second one
         accountUserApi.removeEmail(accountId, new DefaultAccountEmail(accountId, email1), callContext);
-        Assert.assertEquals(accountEmailDao.getEmails(accountId, tenantContext).size(), 1);
+        Assert.assertEquals(accountEmailDao.getByAccountId(accountId, tenantContext).size(), 1);
     }
 }
diff --git a/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java b/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java
index c486939..f28c777 100644
--- a/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java
+++ b/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java
@@ -55,7 +55,7 @@ public abstract class AccountDaoTestBase extends AccountTestSuiteWithEmbeddedDB 
             // Health check test to make sure MySQL is setup properly
             accountDao.test(internalCallContext);
 
-            accountEmailDao = new AuditedAccountEmailDao(dbi, new DefaultClock());
+            accountEmailDao = new AuditedAccountEmailDao(dbi);
             // Health check test to make sure MySQL is setup properly
             accountEmailDao.test(internalCallContext);
         } catch (Throwable t) {
diff --git a/account/src/test/java/com/ning/billing/account/dao/MockAccountEmailDao.java b/account/src/test/java/com/ning/billing/account/dao/MockAccountEmailDao.java
index 1ddc50d..aa4c401 100644
--- a/account/src/test/java/com/ning/billing/account/dao/MockAccountEmailDao.java
+++ b/account/src/test/java/com/ning/billing/account/dao/MockAccountEmailDao.java
@@ -37,7 +37,8 @@ public class MockAccountEmailDao implements AccountEmailDao {
 
     @Override
     public void create(final AccountEmail entity, final InternalCallContext context) throws AccountApiException {
-        saveEmails(entity.getAccountId(), ImmutableList.<AccountEmail>of(entity), context);
+        Set<AccountEmail> theSet = emails.get(entity.getAccountId());
+        theSet.add(entity);
     }
 
     @Override
@@ -56,7 +57,13 @@ public class MockAccountEmailDao implements AccountEmailDao {
     }
 
     @Override
-    public List<AccountEmail> getEmails(final UUID accountId, final InternalTenantContext context) {
+    public void delete(final AccountEmail email, final InternalCallContext context) {
+        Set<AccountEmail> theSet = emails.get(email.getAccountId());
+        theSet.remove(email);
+    }
+
+    @Override
+    public List<AccountEmail> getByAccountId(final UUID accountId, final InternalTenantContext context) {
         final Set<AccountEmail> accountEmails = emails.get(accountId);
         if (accountEmails == null) {
             return ImmutableList.<AccountEmail>of();
@@ -66,33 +73,6 @@ public class MockAccountEmailDao implements AccountEmailDao {
     }
 
     @Override
-    public void saveEmails(final UUID accountId, final List<AccountEmail> newEmails, final InternalCallContext context) {
-        if (emails.get(accountId) == null) {
-            emails.put(accountId, new HashSet<AccountEmail>());
-        }
-
-        emails.get(accountId).addAll(newEmails);
-    }
-
-    @Override
-    public void addEmail(final UUID accountId, final AccountEmail email, final InternalCallContext context) {
-        if (emails.get(accountId) == null) {
-            emails.put(accountId, new HashSet<AccountEmail>());
-        }
-
-        emails.get(accountId).add(email);
-    }
-
-    @Override
-    public void removeEmail(final UUID accountId, final AccountEmail email, final InternalCallContext context) {
-        if (emails.get(accountId) == null) {
-            emails.put(accountId, new HashSet<AccountEmail>());
-        }
-
-        emails.get(accountId).remove(email);
-    }
-
-    @Override
     public void test(final InternalTenantContext context) {
     }
 
diff --git a/account/src/test/java/com/ning/billing/account/dao/TestAccountDao.java b/account/src/test/java/com/ning/billing/account/dao/TestAccountDao.java
index ea01b6a..05168f9 100644
--- a/account/src/test/java/com/ning/billing/account/dao/TestAccountDao.java
+++ b/account/src/test/java/com/ning/billing/account/dao/TestAccountDao.java
@@ -157,8 +157,8 @@ public class TestAccountDao extends AccountDaoTestBase {
         final List<CustomField> customFieldMap = customFieldDao.getCustomFields(accountId, ObjectType.ACCOUNT, internalCallContext);
         assertEquals(customFieldMap.size(), 1);
         final CustomField customField = customFieldMap.get(0);
-        assertEquals(customField.getName(), fieldName);
-        assertEquals(customField.getValue(), fieldValue);
+        assertEquals(customField.getFieldName(), fieldName);
+        assertEquals(customField.getFieldValue(), fieldValue);
     }
 
     @Test(groups = "slow")
@@ -423,20 +423,20 @@ public class TestAccountDao extends AccountDaoTestBase {
     }
 
     @Test(groups = "slow")
-    public void testHandleDuplicateEmails() {
+    public void testHandleDuplicateEmails() throws AccountApiException {
         final UUID accountId = UUID.randomUUID();
         final AccountEmail email = new DefaultAccountEmail(accountId, "test@gmail.com");
-        Assert.assertEquals(accountEmailDao.getEmails(accountId, internalCallContext).size(), 0);
+        Assert.assertEquals(accountEmailDao.getByAccountId(accountId, internalCallContext).size(), 0);
 
-        accountEmailDao.addEmail(accountId, email, internalCallContext);
-        Assert.assertEquals(accountEmailDao.getEmails(accountId, internalCallContext).size(), 1);
+        accountEmailDao.create(email, internalCallContext);
+        Assert.assertEquals(accountEmailDao.getByAccountId(accountId, internalCallContext).size(), 1);
 
-        accountEmailDao.addEmail(accountId, email, internalCallContext);
-        Assert.assertEquals(accountEmailDao.getEmails(accountId, internalCallContext).size(), 1);
+        accountEmailDao.create(email, internalCallContext);
+        Assert.assertEquals(accountEmailDao.getByAccountId(accountId, internalCallContext).size(), 1);
     }
 
     @Test(groups = "slow")
-    public void testAccountEmail() {
+    public void testAccountEmail() throws AccountApiException {
         List<AccountEmail> emails = new ArrayList<AccountEmail>();
 
         // generate random account id
@@ -444,9 +444,8 @@ public class TestAccountDao extends AccountDaoTestBase {
 
         // add a new e-mail
         final AccountEmail email = new DefaultAccountEmail(accountId, "test@gmail.com");
-        emails.add(email);
-        accountEmailDao.saveEmails(accountId, emails, internalCallContext);
-        emails = accountEmailDao.getEmails(accountId, internalCallContext);
+        accountEmailDao.create(email, internalCallContext);
+        emails = accountEmailDao.getByAccountId(accountId, internalCallContext);
         assertEquals(emails.size(), 1);
 
         // verify that history and audit contain one entry
@@ -456,8 +455,8 @@ public class TestAccountDao extends AccountDaoTestBase {
         final AccountEmail updatedEmail = new DefaultAccountEmail(email, "test2@gmail.com");
         emails.clear();
         emails.add(updatedEmail);
-        accountEmailDao.saveEmails(accountId, emails, internalCallContext);
-        emails = accountEmailDao.getEmails(accountId, internalCallContext);
+        accountEmailDao.create(updatedEmail, internalCallContext);
+        emails = accountEmailDao.getByAccountId(accountId, internalCallContext);
         assertEquals(emails.size(), 1);
 
         // verify that history and audit contain three entries
@@ -465,8 +464,10 @@ public class TestAccountDao extends AccountDaoTestBase {
         verifyAccountEmailAuditAndHistoryCount(accountId, 3);
 
         // delete e-mail
-        accountEmailDao.saveEmails(accountId, new ArrayList<AccountEmail>(), internalCallContext);
-        emails = accountEmailDao.getEmails(accountId, internalCallContext);
+        accountEmailDao.delete(email, internalCallContext);
+        accountEmailDao.delete(updatedEmail, internalCallContext);
+
+        emails = accountEmailDao.getByAccountId(accountId, internalCallContext);
         assertEquals(emails.size(), 0);
 
         // verify that history and audit contain four entries
@@ -474,26 +475,26 @@ public class TestAccountDao extends AccountDaoTestBase {
     }
 
     @Test(groups = "slow")
-    public void testAddAndRemoveAccountEmail() {
+    public void testAddAndRemoveAccountEmail() throws AccountApiException {
         final UUID accountId = UUID.randomUUID();
         final String email1 = UUID.randomUUID().toString();
         final String email2 = UUID.randomUUID().toString();
 
         // Verify the original state
-        assertEquals(accountEmailDao.getEmails(accountId, internalCallContext).size(), 0);
+        assertEquals(accountEmailDao.getByAccountId(accountId, internalCallContext).size(), 0);
 
         // Add a new e-mail
         final AccountEmail accountEmail1 = new DefaultAccountEmail(accountId, email1);
-        accountEmailDao.addEmail(accountId, accountEmail1, internalCallContext);
-        final List<AccountEmail> firstEmails = accountEmailDao.getEmails(accountId, internalCallContext);
+        accountEmailDao.create(accountEmail1, internalCallContext);
+        final List<AccountEmail> firstEmails = accountEmailDao.getByAccountId(accountId, internalCallContext);
         assertEquals(firstEmails.size(), 1);
         assertEquals(firstEmails.get(0).getAccountId(), accountId);
         assertEquals(firstEmails.get(0).getEmail(), email1);
 
         // Add a second e-mail
         final AccountEmail accountEmail2 = new DefaultAccountEmail(accountId, email2);
-        accountEmailDao.addEmail(accountId, accountEmail2, internalCallContext);
-        final List<AccountEmail> secondEmails = accountEmailDao.getEmails(accountId, internalCallContext);
+        accountEmailDao.create(accountEmail2, internalCallContext);
+        final List<AccountEmail> secondEmails = accountEmailDao.getByAccountId(accountId, internalCallContext);
         assertEquals(secondEmails.size(), 2);
         assertTrue(secondEmails.get(0).getAccountId().equals(accountId));
         assertTrue(secondEmails.get(1).getAccountId().equals(accountId));
@@ -501,8 +502,8 @@ public class TestAccountDao extends AccountDaoTestBase {
         assertTrue(secondEmails.get(1).getEmail().equals(email1) || secondEmails.get(1).getEmail().equals(email2));
 
         // Delete the first e-mail
-        accountEmailDao.removeEmail(accountId, accountEmail1, internalCallContext);
-        final List<AccountEmail> thirdEmails = accountEmailDao.getEmails(accountId, internalCallContext);
+        accountEmailDao.delete(accountEmail1, internalCallContext);
+        final List<AccountEmail> thirdEmails = accountEmailDao.getByAccountId(accountId, internalCallContext);
         assertEquals(thirdEmails.size(), 1);
         assertEquals(thirdEmails.get(0).getAccountId(), accountId);
         assertEquals(thirdEmails.get(0).getEmail(), email2);
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessTagRecorder.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessTagRecorder.java
index a38ba20..ccd29e0 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessTagRecorder.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessTagRecorder.java
@@ -86,7 +86,7 @@ public class TestBusinessTagRecorder extends AnalyticsTestSuiteWithEmbeddedDB {
         subscriptionTransitionTagSqlDao = dbi.onDemand(BusinessSubscriptionTransitionTagSqlDao.class);
         eventBus = new InMemoryInternalBus();
         final AccountDao accountDao = new AuditedAccountDao(dbi, eventBus, new InternalCallContextFactory(dbi, new ClockMock()));
-        final AccountEmailDao accountEmailDao = new AuditedAccountEmailDao(dbi, clock);
+        final AccountEmailDao accountEmailDao = new AuditedAccountEmailDao(dbi);
         callContextFactory = new DefaultCallContextFactory(clock);
         final InternalCallContextFactory internalCallContextFactory = new InternalCallContextFactory(dbi, clock);
         accountApi = new DefaultAccountInternalApi(accountDao, accountEmailDao);
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountUserApi.java b/api/src/main/java/com/ning/billing/account/api/AccountUserApi.java
index da18c28..d21e392 100644
--- a/api/src/main/java/com/ning/billing/account/api/AccountUserApi.java
+++ b/api/src/main/java/com/ning/billing/account/api/AccountUserApi.java
@@ -131,7 +131,7 @@ public interface AccountUserApi {
      * @param email     the email to be added
      * @param context   the user context
      */
-    public void addEmail(UUID accountId, AccountEmail email, CallContext context);
+    public void addEmail(UUID accountId, AccountEmail email, CallContext context) throws AccountApiException;
 
     /**
      *
diff --git a/api/src/main/java/com/ning/billing/ErrorCode.java b/api/src/main/java/com/ning/billing/ErrorCode.java
index 0230f96..b694ab2 100644
--- a/api/src/main/java/com/ning/billing/ErrorCode.java
+++ b/api/src/main/java/com/ning/billing/ErrorCode.java
@@ -156,6 +156,8 @@ public enum ErrorCode {
     ACCOUNT_CREATION_FAILED(3006, "Account creation failed."),
     ACCOUNT_UPDATE_FAILED(3007, "Account update failed."),
 
+    ACCOUNT_EMAIL_ALREADY_EXISTS(3500, "Account email already exists %s"),
+
     /*
     *
     * Range 3900: Tag definitions
diff --git a/api/src/main/java/com/ning/billing/util/customfield/CustomField.java b/api/src/main/java/com/ning/billing/util/customfield/CustomField.java
index 566dcd5..0831ee5 100644
--- a/api/src/main/java/com/ning/billing/util/customfield/CustomField.java
+++ b/api/src/main/java/com/ning/billing/util/customfield/CustomField.java
@@ -27,9 +27,8 @@ public interface CustomField extends Entity {
 
     public ObjectType getObjectType();
 
-    public String getName();
+    public String getFieldName();
 
-    public String getValue();
+    public String getFieldValue();
 
-    public void setValue(String value);
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/BundleSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/BundleSqlDao.java
index 7721512..65671d4 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/BundleSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/BundleSqlDao.java
@@ -23,10 +23,9 @@ import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
-import org.skife.jdbi.v2.SQLStatement;
 import org.skife.jdbi.v2.StatementContext;
 import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BindBean;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
 import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
@@ -37,8 +36,6 @@ import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
 import com.ning.billing.util.audit.ChangeType;
 import com.ning.billing.util.callcontext.InternalCallContext;
 import com.ning.billing.util.callcontext.InternalTenantContext;
-import com.ning.billing.util.callcontext.InternalTenantContextBinder;
-import com.ning.billing.util.dao.BinderBase;
 import com.ning.billing.util.dao.MapperBase;
 import com.ning.billing.util.entity.dao.Audited;
 import com.ning.billing.util.entity.dao.EntitySqlDao;
@@ -47,45 +44,38 @@ import com.ning.billing.util.entity.dao.EntitySqlDaoStringTemplate;
 @EntitySqlDaoStringTemplate
 @RegisterMapper(BundleSqlDao.ISubscriptionBundleSqlMapper.class)
 public interface BundleSqlDao extends EntitySqlDao<SubscriptionBundle> {
+
     @SqlUpdate
     @Audited(ChangeType.INSERT)
-    public void insertBundle(@Bind(binder = SubscriptionBundleBinder.class) SubscriptionBundleData bundle,
-                             @InternalTenantContextBinder final InternalCallContext context);
+    public void insertBundle(@BindBean SubscriptionBundleData bundle,
+                             @BindBean final InternalCallContext context);
 
     @SqlUpdate
     @Audited(ChangeType.UPDATE)
     public void updateBundleLastSysTime(@Bind("id") String id,
                                         @Bind("lastSysUpdateDate") Date lastSysUpdate,
-                                        @InternalTenantContextBinder final InternalCallContext context);
+                                        @BindBean final InternalCallContext context);
 
     @SqlQuery
     public SubscriptionBundle getBundleFromId(@Bind("id") String id,
-                                              @InternalTenantContextBinder final InternalTenantContext context);
+                                              @BindBean final InternalTenantContext context);
 
     @SqlQuery
     public SubscriptionBundle getBundleFromAccountAndKey(@Bind("accountId") String accountId,
                                                          @Bind("externalKey") String externalKey,
-                                                         @InternalTenantContextBinder final InternalTenantContext context);
+                                                         @BindBean final InternalTenantContext context);
 
     @SqlQuery
     public List<SubscriptionBundle> getBundleFromAccount(@Bind("accountId") String accountId,
-                                                         @InternalTenantContextBinder final InternalTenantContext context);
+                                                         @BindBean final InternalTenantContext context);
 
     @SqlQuery
     public List<SubscriptionBundle> getBundlesForKey(@Bind("externalKey") String externalKey,
-                                                     @InternalTenantContextBinder final InternalTenantContext context);
+                                                     @BindBean final InternalTenantContext context);
 
-    public static class SubscriptionBundleBinder extends BinderBase implements Binder<Bind, SubscriptionBundleData> {
-        @Override
-        public void bind(@SuppressWarnings("rawtypes") final SQLStatement stmt, final Bind bind, final SubscriptionBundleData bundle) {
-            stmt.bind("id", bundle.getId().toString());
-            stmt.bind("externalKey", bundle.getKey());
-            stmt.bind("accountId", bundle.getAccountId().toString());
-            stmt.bind("lastSysUpdateDate", getDate(bundle.getLastSysUpdateTime()));
-        }
-    }
 
     public static class ISubscriptionBundleSqlMapper extends MapperBase implements ResultSetMapper<SubscriptionBundle> {
+
         @Override
         public SubscriptionBundle map(final int arg, final ResultSet r, final StatementContext ctx) throws SQLException {
             final UUID id = UUID.fromString(r.getString("id"));
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementEventSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementEventSqlDao.java
index 45d8271..8a45d11 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementEventSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementEventSqlDao.java
@@ -23,10 +23,9 @@ import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
-import org.skife.jdbi.v2.SQLStatement;
 import org.skife.jdbi.v2.StatementContext;
 import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BindBean;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
 import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
@@ -36,10 +35,8 @@ import com.ning.billing.entitlement.engine.dao.EntitlementEventSqlDao.EventSqlMa
 import com.ning.billing.entitlement.events.EntitlementEvent;
 import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
 import com.ning.billing.entitlement.events.EventBaseBuilder;
-import com.ning.billing.entitlement.events.phase.PhaseEvent;
 import com.ning.billing.entitlement.events.phase.PhaseEventBuilder;
 import com.ning.billing.entitlement.events.phase.PhaseEventData;
-import com.ning.billing.entitlement.events.user.ApiEvent;
 import com.ning.billing.entitlement.events.user.ApiEventBuilder;
 import com.ning.billing.entitlement.events.user.ApiEventCancel;
 import com.ning.billing.entitlement.events.user.ApiEventChange;
@@ -54,8 +51,6 @@ import com.ning.billing.entitlement.exceptions.EntitlementError;
 import com.ning.billing.util.audit.ChangeType;
 import com.ning.billing.util.callcontext.InternalCallContext;
 import com.ning.billing.util.callcontext.InternalTenantContext;
-import com.ning.billing.util.callcontext.InternalTenantContextBinder;
-import com.ning.billing.util.dao.BinderBase;
 import com.ning.billing.util.dao.MapperBase;
 import com.ning.billing.util.entity.dao.Audited;
 import com.ning.billing.util.entity.dao.EntitySqlDao;
@@ -67,69 +62,38 @@ public interface EntitlementEventSqlDao extends EntitySqlDao<EntitlementEvent> {
 
     @SqlQuery
     public EntitlementEvent getEventById(@Bind("id") String id,
-                                         @InternalTenantContextBinder final InternalTenantContext context);
+                                         @BindBean final InternalTenantContext context);
 
     @SqlUpdate
     @Audited(ChangeType.INSERT)
-    public void insertEvent(@Bind(binder = EventSqlDaoBinder.class) EntitlementEvent evt,
-                            @InternalTenantContextBinder final InternalCallContext context);
+    public void insertEvent(@BindBean final EntitlementEvent evt,
+                            @BindBean final InternalCallContext context);
 
     @SqlUpdate
     @Audited(ChangeType.UPDATE)
     public void unactiveEvent(@Bind("id") String id,
-                              @InternalTenantContextBinder final InternalCallContext context);
+                              @BindBean final InternalCallContext context);
 
     @SqlUpdate
     @Audited(ChangeType.UPDATE)
     public void reactiveEvent(@Bind("id") String id,
-                              @InternalTenantContextBinder final InternalCallContext context);
+                              @BindBean final InternalCallContext context);
 
     @SqlUpdate
     @Audited(ChangeType.UPDATE)
     public void updateVersion(@Bind("id") String id,
                               @Bind("currentVersion") Long currentVersion,
-                              @InternalTenantContextBinder final InternalCallContext context);
+                              @BindBean final InternalCallContext context);
 
     @SqlQuery
     public List<EntitlementEvent> getFutureActiveEventForSubscription(@Bind("subscriptionId") String subscriptionId,
                                                                       @Bind("now") Date now,
-                                                                      @InternalTenantContextBinder final InternalTenantContext context);
+                                                                      @BindBean final InternalTenantContext context);
 
     @SqlQuery
     public List<EntitlementEvent> getEventsForSubscription(@Bind("subscriptionId") String subscriptionId,
-                                                           @InternalTenantContextBinder final InternalTenantContext context);
+                                                           @BindBean final InternalTenantContext context);
 
-    public static class EventSqlDaoBinder extends BinderBase implements Binder<Bind, EntitlementEvent> {
-
-        @Override
-        public void bind(@SuppressWarnings("rawtypes") final SQLStatement stmt, final Bind bind, final EntitlementEvent evt) {
-
-            String planName = null;
-            String phaseName = null;
-            String priceListName = null;
-            String userType = null;
-            if (evt.getType() == EventType.API_USER) {
-                final ApiEvent userEvent = (ApiEvent) evt;
-                planName = userEvent.getEventPlan();
-                phaseName = userEvent.getEventPlanPhase();
-                priceListName = userEvent.getPriceList();
-                userType = userEvent.getEventType().toString();
-            } else {
-                phaseName = ((PhaseEvent) evt).getPhase();
-            }
-            stmt.bind("id", evt.getId().toString());
-            stmt.bind("eventType", evt.getType().toString());
-            stmt.bind("userType", userType);
-            stmt.bind("requestedDate", getDate(evt.getRequestedDate()));
-            stmt.bind("effectiveDate", getDate(evt.getEffectiveDate()));
-            stmt.bind("subscriptionId", evt.getSubscriptionId().toString());
-            stmt.bind("planName", planName);
-            stmt.bind("phaseName", phaseName);
-            stmt.bind("priceListName", priceListName);
-            stmt.bind("currentVersion", evt.getActiveVersion());
-            stmt.bind("isActive", evt.isActive());
-        }
-    }
 
     public static class EventSqlMapper extends MapperBase implements ResultSetMapper<EntitlementEvent> {
 
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.java
index 84e3aa2..e7545eb 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.java
@@ -26,6 +26,7 @@ import org.joda.time.DateTime;
 import org.skife.jdbi.v2.SQLStatement;
 import org.skife.jdbi.v2.StatementContext;
 import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.BindBean;
 import org.skife.jdbi.v2.sqlobject.Binder;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
@@ -53,48 +54,34 @@ public interface SubscriptionSqlDao extends EntitySqlDao<Subscription> {
 
     @SqlUpdate
     @Audited(ChangeType.INSERT)
-    public void insertSubscription(@Bind(binder = SubscriptionBinder.class) SubscriptionData sub,
-                                   @InternalTenantContextBinder final InternalCallContext context);
+    public void insertSubscription(@BindBean SubscriptionData sub,
+                                   @BindBean final InternalCallContext context);
 
     @SqlQuery
     public Subscription getSubscriptionFromId(@Bind("id") String id,
-                                              @InternalTenantContextBinder final InternalTenantContext context);
+                                              @BindBean final InternalTenantContext context);
 
     @SqlQuery
     public List<Subscription> getSubscriptionsFromBundleId(@Bind("bundleId") String bundleId,
-                                                           @InternalTenantContextBinder final InternalTenantContext context);
+                                                           @BindBean final InternalTenantContext context);
 
     @SqlUpdate
     @Audited(ChangeType.UPDATE)
     public void updateChargedThroughDate(@Bind("id") String id, @Bind("chargedThroughDate") Date chargedThroughDate,
-                                         @InternalTenantContextBinder final InternalCallContext context);
+                                         @BindBean final InternalCallContext context);
 
     @SqlUpdate
     @Audited(ChangeType.UPDATE)
     void updateActiveVersion(@Bind("id") String id, @Bind("activeVersion") long activeVersion,
-                             @InternalTenantContextBinder final InternalCallContext context);
+                             @BindBean final InternalCallContext context);
 
     @SqlUpdate
     @Audited(ChangeType.UPDATE)
     public void updateForRepair(@Bind("id") String id, @Bind("activeVersion") long activeVersion,
                                 @Bind("startDate") Date startDate,
                                 @Bind("bundleStartDate") Date bundleStartDate,
-                                @InternalTenantContextBinder final InternalCallContext context);
+                                @BindBean final InternalCallContext context);
 
-    public static class SubscriptionBinder extends BinderBase implements Binder<Bind, SubscriptionData> {
-
-        @Override
-        public void bind(@SuppressWarnings("rawtypes") final SQLStatement stmt, final Bind bind, final SubscriptionData sub) {
-            stmt.bind("id", sub.getId().toString());
-            stmt.bind("bundleId", sub.getBundleId().toString());
-            stmt.bind("category", sub.getCategory().toString());
-            stmt.bind("startDate", getDate(sub.getAlignStartDate()));
-            stmt.bind("bundleStartDate", getDate(sub.getBundleStartDate()));
-            stmt.bind("activeVersion", sub.getActiveVersion());
-            stmt.bind("chargedThroughDate", getDate(sub.getChargedThroughDate()));
-            stmt.bind("paidThroughDate", getDate(sub.getPaidThroughDate()));
-        }
-    }
 
     public static class SubscriptionMapper extends MapperBase implements ResultSetMapper<Subscription> {
 
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
index 6351f3a..d64fe91 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
@@ -16,11 +16,6 @@
 
 package com.ning.billing.invoice.dao;
 
-import java.lang.annotation.Annotation;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
 import java.math.BigDecimal;
 import java.sql.ResultSet;
 import java.sql.SQLException;
@@ -29,12 +24,9 @@ import java.util.UUID;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
-import org.skife.jdbi.v2.SQLStatement;
 import org.skife.jdbi.v2.StatementContext;
 import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.Binder;
-import org.skife.jdbi.v2.sqlobject.BinderFactory;
-import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+import org.skife.jdbi.v2.sqlobject.BindBean;
 import org.skife.jdbi.v2.sqlobject.SqlBatch;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
@@ -55,7 +47,6 @@ import com.ning.billing.invoice.model.RepairAdjInvoiceItem;
 import com.ning.billing.util.audit.ChangeType;
 import com.ning.billing.util.callcontext.InternalCallContext;
 import com.ning.billing.util.callcontext.InternalTenantContext;
-import com.ning.billing.util.callcontext.InternalTenantContextBinder;
 import com.ning.billing.util.dao.MapperBase;
 import com.ning.billing.util.entity.dao.Audited;
 import com.ning.billing.util.entity.dao.EntitySqlDao;
@@ -67,62 +58,31 @@ public interface InvoiceItemSqlDao extends EntitySqlDao<InvoiceItem> {
 
     @SqlQuery
     List<Long> getRecordIds(@Bind("invoiceId") final String invoiceId,
-                            @InternalTenantContextBinder final InternalTenantContext context);
+                            @BindBean final InternalTenantContext context);
 
     @SqlQuery
     List<InvoiceItem> getInvoiceItemsByInvoice(@Bind("invoiceId") final String invoiceId,
-                                               @InternalTenantContextBinder final InternalTenantContext context);
+                                               @BindBean final InternalTenantContext context);
 
     @SqlQuery
     List<InvoiceItem> getInvoiceItemsByAccount(@Bind("accountId") final String accountId,
-                                               @InternalTenantContextBinder final InternalTenantContext context);
+                                               @BindBean final InternalTenantContext context);
 
     @SqlQuery
     List<InvoiceItem> getInvoiceItemsBySubscription(@Bind("subscriptionId") final String subscriptionId,
-                                                    @InternalTenantContextBinder final InternalTenantContext context);
+                                                    @BindBean final InternalTenantContext context);
 
     @Override
     @SqlUpdate
     @Audited(ChangeType.INSERT)
-    void create(@InvoiceItemBinder final InvoiceItem invoiceItem,
-                @InternalTenantContextBinder final InternalCallContext context);
+    void create(@BindBean final InvoiceItem invoiceItem,
+                @BindBean final InternalCallContext context);
 
     @SqlBatch(transactional = false)
     @Audited(ChangeType.INSERT)
-    void batchCreateFromTransaction(@InvoiceItemBinder final List<InvoiceItem> items,
-                                    @InternalTenantContextBinder final InternalCallContext context);
+    void batchCreateFromTransaction(@BindBean final List<InvoiceItem> items,
+                                    @BindBean final InternalCallContext context);
 
-    @BindingAnnotation(InvoiceItemBinder.InvoiceItemBinderFactory.class)
-    @Retention(RetentionPolicy.RUNTIME)
-    @Target({ElementType.PARAMETER})
-    public @interface InvoiceItemBinder {
-
-        public static class InvoiceItemBinderFactory implements BinderFactory {
-
-            @Override
-            public Binder build(final Annotation annotation) {
-                return new Binder<InvoiceItemBinder, InvoiceItem>() {
-                    @Override
-                    public void bind(final SQLStatement<?> q, final InvoiceItemBinder bind, final InvoiceItem item) {
-                        q.bind("id", item.getId().toString());
-                        q.bind("type", item.getInvoiceItemType().toString());
-                        q.bind("invoiceId", item.getInvoiceId().toString());
-                        q.bind("accountId", item.getAccountId().toString());
-                        q.bind("bundleId", item.getBundleId() == null ? null : item.getBundleId().toString());
-                        q.bind("subscriptionId", item.getSubscriptionId() == null ? null : item.getSubscriptionId().toString());
-                        q.bind("planName", item.getPlanName() == null ? null : item.getPlanName());
-                        q.bind("phaseName", item.getPhaseName() == null ? item.getPhaseName() : item.getPhaseName());
-                        q.bind("startDate", item.getStartDate().toDate());
-                        q.bind("endDate", item.getEndDate() == null ? null : item.getEndDate().toDate());
-                        q.bind("amount", item.getAmount());
-                        q.bind("rate", (item.getRate() == null) ? null : item.getRate());
-                        q.bind("currency", item.getCurrency().toString());
-                        q.bind("linkedItemId", (item.getLinkedItemId() == null) ? null : item.getLinkedItemId().toString());
-                    }
-                };
-            }
-        }
-    }
 
     public static class InvoiceItemSqlDaoMapper extends MapperBase implements ResultSetMapper<InvoiceItem> {
 
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java
index 7445c57..4670035 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java
@@ -16,11 +16,6 @@
 
 package com.ning.billing.invoice.dao;
 
-import java.lang.annotation.Annotation;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
 import java.math.BigDecimal;
 import java.sql.ResultSet;
 import java.sql.SQLException;
@@ -28,12 +23,9 @@ import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
-import org.skife.jdbi.v2.SQLStatement;
 import org.skife.jdbi.v2.StatementContext;
 import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.Binder;
-import org.skife.jdbi.v2.sqlobject.BinderFactory;
-import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+import org.skife.jdbi.v2.sqlobject.BindBean;
 import org.skife.jdbi.v2.sqlobject.SqlBatch;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
@@ -47,8 +39,6 @@ import com.ning.billing.invoice.model.DefaultInvoicePayment;
 import com.ning.billing.util.audit.ChangeType;
 import com.ning.billing.util.callcontext.InternalCallContext;
 import com.ning.billing.util.callcontext.InternalTenantContext;
-import com.ning.billing.util.callcontext.InternalTenantContextBinder;
-import com.ning.billing.util.dao.BinderBase;
 import com.ning.billing.util.dao.MapperBase;
 import com.ning.billing.util.dao.UuidMapper;
 import com.ning.billing.util.entity.dao.Audited;
@@ -61,60 +51,60 @@ public interface InvoicePaymentSqlDao extends EntitySqlDao<InvoicePayment> {
 
     @SqlQuery
     List<Long> getRecordIds(@Bind("invoiceId") final String invoiceId,
-                            @InternalTenantContextBinder final InternalTenantContext context);
+                            @BindBean final InternalTenantContext context);
 
     @SqlQuery
     public InvoicePayment getByPaymentId(@Bind("paymentId") final String paymentId,
-                                         @InternalTenantContextBinder final InternalTenantContext context);
+                                         @BindBean final InternalTenantContext context);
 
     @Override
     @SqlQuery
-    public List<InvoicePayment> get(@InternalTenantContextBinder final InternalTenantContext context);
+    public List<InvoicePayment> get(@BindBean final InternalTenantContext context);
 
     @Override
     @SqlUpdate
     @Audited(ChangeType.INSERT)
-    public void create(@InvoicePaymentBinder final InvoicePayment invoicePayment,
-                       @InternalTenantContextBinder final InternalCallContext context);
+    public void create(@BindBean final InvoicePayment invoicePayment,
+                       @BindBean final InternalCallContext context);
 
     @SqlBatch(transactional = false)
     @Audited(ChangeType.INSERT)
-    void batchCreateFromTransaction(@InvoicePaymentBinder final List<InvoicePayment> items,
-                                    @InternalTenantContextBinder final InternalCallContext context);
+    void batchCreateFromTransaction(@BindBean final List<InvoicePayment> items,
+                                    @BindBean final InternalCallContext context);
 
     @SqlQuery
     public List<InvoicePayment> getPaymentsForInvoice(@Bind("invoiceId") final String invoiceId,
-                                                      @InternalTenantContextBinder final InternalTenantContext context);
+                                                      @BindBean final InternalTenantContext context);
 
     @SqlQuery
     List<InvoicePayment> getInvoicePayments(@Bind("paymentId") final String paymentId,
-                                            @InternalTenantContextBinder final InternalTenantContext context);
+                                            @BindBean final InternalTenantContext context);
 
     @SqlQuery
     InvoicePayment getPaymentsForCookieId(@Bind("paymentCookieId") final String paymentCookieId,
-                                          @InternalTenantContextBinder final InternalTenantContext context);
+                                          @BindBean final InternalTenantContext context);
 
     @SqlUpdate
     @Audited(ChangeType.UPDATE)
-    void notifyOfPayment(@InvoicePaymentBinder final InvoicePayment invoicePayment,
-                         @InternalTenantContextBinder final InternalCallContext context);
+    void notifyOfPayment(@BindBean final InvoicePayment invoicePayment,
+                         @BindBean final InternalCallContext context);
 
     @SqlQuery
     BigDecimal getRemainingAmountPaid(@Bind("invoicePaymentId") final String invoicePaymentId,
-                                      @InternalTenantContextBinder final InternalTenantContext context);
+                                      @BindBean final InternalTenantContext context);
 
     @SqlQuery
     @RegisterMapper(UuidMapper.class)
     UUID getAccountIdFromInvoicePaymentId(@Bind("invoicePaymentId") final String invoicePaymentId,
-                                          @InternalTenantContextBinder final InternalTenantContext context);
+                                          @BindBean final InternalTenantContext context);
 
     @SqlQuery
     List<InvoicePayment> getChargeBacksByAccountId(@Bind("accountId") final String accountId,
-                                                   @InternalTenantContextBinder final InternalTenantContext context);
+                                                   @BindBean final InternalTenantContext context);
 
     @SqlQuery
     List<InvoicePayment> getChargebacksByPaymentId(@Bind("paymentId") final String paymentId,
-                                                   @InternalTenantContextBinder final InternalTenantContext context);
+                                                   @BindBean final InternalTenantContext context);
 
     public static class InvoicePaymentMapper extends MapperBase implements ResultSetMapper<InvoicePayment> {
 
@@ -136,32 +126,4 @@ public interface InvoicePaymentSqlDao extends EntitySqlDao<InvoicePayment> {
                                              amount, currency, paymentCookieId, linkedInvoicePaymentId);
         }
     }
-
-    @BindingAnnotation(InvoicePaymentBinder.InvoicePaymentBinderFactory.class)
-    @Retention(RetentionPolicy.RUNTIME)
-    @Target({ElementType.PARAMETER})
-    public @interface InvoicePaymentBinder {
-
-        public static class InvoicePaymentBinderFactory extends BinderBase implements BinderFactory {
-
-            @Override
-            public Binder build(final Annotation annotation) {
-                return new Binder<InvoicePaymentBinder, InvoicePayment>() {
-                    @Override
-                    public void bind(final SQLStatement q, final InvoicePaymentBinder bind, final InvoicePayment payment) {
-                        q.bind("id", payment.getId().toString());
-                        q.bind("type", payment.getType().toString());
-                        q.bind("invoiceId", payment.getInvoiceId().toString());
-                        q.bind("paymentId", uuidToString(payment.getPaymentId()));
-                        q.bind("paymentDate", payment.getPaymentDate().toDate());
-                        q.bind("amount", payment.getAmount());
-                        final Currency currency = payment.getCurrency();
-                        q.bind("currency", (currency == null) ? null : currency.toString());
-                        q.bind("paymentCookieId", uuidToString(payment.getPaymentCookieId()));
-                        q.bind("linkedInvoicePaymentId", uuidToString(payment.getLinkedInvoicePaymentId()));
-                    }
-                };
-            }
-        }
-    }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java
index 505b15c..dae8456 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java
@@ -16,11 +16,6 @@
 
 package com.ning.billing.invoice.dao;
 
-import java.lang.annotation.Annotation;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.Date;
@@ -29,12 +24,9 @@ import java.util.UUID;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
-import org.skife.jdbi.v2.SQLStatement;
 import org.skife.jdbi.v2.StatementContext;
 import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.Binder;
-import org.skife.jdbi.v2.sqlobject.BinderFactory;
-import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+import org.skife.jdbi.v2.sqlobject.BindBean;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
 import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
@@ -46,7 +38,6 @@ import com.ning.billing.invoice.model.DefaultInvoice;
 import com.ning.billing.util.audit.ChangeType;
 import com.ning.billing.util.callcontext.InternalCallContext;
 import com.ning.billing.util.callcontext.InternalTenantContext;
-import com.ning.billing.util.callcontext.InternalTenantContextBinder;
 import com.ning.billing.util.dao.MapperBase;
 import com.ning.billing.util.dao.UuidMapper;
 import com.ning.billing.util.entity.dao.Audited;
@@ -60,53 +51,30 @@ public interface InvoiceSqlDao extends EntitySqlDao<Invoice> {
     @Override
     @SqlUpdate
     @Audited(ChangeType.INSERT)
-    void create(@InvoiceBinder Invoice invoice,
-                @InternalTenantContextBinder final InternalCallContext context);
+    void create(@BindBean Invoice invoice,
+                @BindBean final InternalCallContext context);
 
     @SqlQuery
     List<Invoice> getInvoicesByAccount(@Bind("accountId") final String accountId,
-                                       @InternalTenantContextBinder final InternalTenantContext context);
+                                       @BindBean final InternalTenantContext context);
 
     @SqlQuery
     List<Invoice> getAllInvoicesByAccount(@Bind("accountId") final String string,
-                                          @InternalTenantContextBinder final InternalTenantContext context);
+                                          @BindBean final InternalTenantContext context);
 
     @SqlQuery
     List<Invoice> getInvoicesByAccountAfterDate(@Bind("accountId") final String accountId,
                                                 @Bind("fromDate") final Date fromDate,
-                                                @InternalTenantContextBinder final InternalTenantContext context);
+                                                @BindBean final InternalTenantContext context);
 
     @SqlQuery
     List<Invoice> getInvoicesBySubscription(@Bind("subscriptionId") final String subscriptionId,
-                                            @InternalTenantContextBinder final InternalTenantContext context);
+                                            @BindBean final InternalTenantContext context);
 
     @SqlQuery
     UUID getInvoiceIdByPaymentId(@Bind("paymentId") final String paymentId,
-                                 @InternalTenantContextBinder final InternalTenantContext context);
-
-    @BindingAnnotation(InvoiceBinder.InvoiceBinderFactory.class)
-    @Retention(RetentionPolicy.RUNTIME)
-    @Target({ElementType.PARAMETER})
-    public @interface InvoiceBinder {
-
-        public static class InvoiceBinderFactory implements BinderFactory {
-
-            @Override
-            public Binder<InvoiceBinder, Invoice> build(final Annotation annotation) {
-                return new Binder<InvoiceBinder, Invoice>() {
-                    @Override
-                    public void bind(@SuppressWarnings("rawtypes") final SQLStatement q, final InvoiceBinder bind, final Invoice invoice) {
-                        q.bind("id", invoice.getId().toString());
-                        q.bind("accountId", invoice.getAccountId().toString());
-                        q.bind("invoiceDate", invoice.getInvoiceDate().toDate());
-                        q.bind("targetDate", invoice.getTargetDate().toDate());
-                        q.bind("currency", invoice.getCurrency().toString());
-                        q.bind("migrated", invoice.isMigrationInvoice());
-                    }
-                };
-            }
-        }
-    }
+                                 @BindBean final InternalTenantContext context);
+
 
     public static class InvoiceMapper extends MapperBase implements ResultSetMapper<Invoice> {
 
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/CustomFieldJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/CustomFieldJson.java
index d6daf76..a72db5c 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/CustomFieldJson.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/CustomFieldJson.java
@@ -37,8 +37,8 @@ public class CustomFieldJson {
     }
 
     public CustomFieldJson(final CustomField input) {
-        this.name = input.getName();
-        this.value = input.getValue();
+        this.name = input.getFieldName();
+        this.value = input.getFieldValue();
     }
 
     public String getName() {
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccountUserApi.java b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccountUserApi.java
index 3d8a7ab..85d01cc 100644
--- a/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccountUserApi.java
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccountUserApi.java
@@ -101,7 +101,7 @@ public class BlockingAccountUserApi implements AccountUserApi {
     }
 
     @Override
-    public void addEmail(final UUID accountId, final AccountEmail email, final CallContext context) {
+    public void addEmail(final UUID accountId, final AccountEmail email, final CallContext context) throws AccountApiException {
         userApi.addEmail(accountId, email, context);
     }
 
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentAttemptSqlDao.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentAttemptSqlDao.java
index 51ad762..551069a 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/PaymentAttemptSqlDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentAttemptSqlDao.java
@@ -26,33 +26,32 @@ import org.joda.time.DateTime;
 import org.skife.jdbi.v2.SQLStatement;
 import org.skife.jdbi.v2.StatementContext;
 import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.BindBean;
 import org.skife.jdbi.v2.sqlobject.Binder;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
 import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
-import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
 import org.skife.jdbi.v2.tweak.ResultSetMapper;
 
+import com.ning.billing.payment.api.Payment.PaymentAttempt;
 import com.ning.billing.payment.api.PaymentStatus;
 import com.ning.billing.util.audit.ChangeType;
 import com.ning.billing.util.callcontext.InternalCallContext;
 import com.ning.billing.util.callcontext.InternalTenantContext;
-import com.ning.billing.util.callcontext.InternalTenantContextBinder;
 import com.ning.billing.util.dao.BinderBase;
-import com.ning.billing.util.dao.EntityHistory;
 import com.ning.billing.util.dao.MapperBase;
 import com.ning.billing.util.entity.dao.Audited;
+import com.ning.billing.util.entity.dao.EntitySqlDao;
 import com.ning.billing.util.entity.dao.EntitySqlDaoStringTemplate;
-import com.ning.billing.util.entity.dao.UpdatableEntitySqlDao;
 
 @EntitySqlDaoStringTemplate
 @RegisterMapper(PaymentAttemptSqlDao.PaymentAttemptModelDaoMapper.class)
-public interface PaymentAttemptSqlDao extends UpdatableEntitySqlDao<PaymentAttemptModelDao>, CloseMe {
+public interface PaymentAttemptSqlDao extends EntitySqlDao<PaymentAttempt> {
 
     @SqlUpdate
     @Audited(ChangeType.INSERT)
-    void insertPaymentAttempt(@Bind(binder = PaymentAttemptModelDaoBinder.class) final PaymentAttemptModelDao attempt,
-                              @InternalTenantContextBinder final InternalCallContext context);
+    void insertPaymentAttempt(@BindBean final PaymentAttemptModelDao attempt,
+                              @BindBean final InternalCallContext context);
 
     @SqlUpdate
     @Audited(ChangeType.UPDATE)
@@ -60,33 +59,16 @@ public interface PaymentAttemptSqlDao extends UpdatableEntitySqlDao<PaymentAttem
                                     @Bind("processingStatus") final String processingStatus,
                                     @Bind("gatewayErrorCode") final String gatewayErrorCode,
                                     @Bind("gatewayErrorMsg") final String gatewayErrorMsg,
-                                    @InternalTenantContextBinder final InternalCallContext context);
+                                    @BindBean final InternalCallContext context);
 
     @SqlQuery
     PaymentAttemptModelDao getPaymentAttempt(@Bind("id") final String attemptId,
-                                             @InternalTenantContextBinder final InternalTenantContext context);
+                                             @BindBean final InternalTenantContext context);
 
     @SqlQuery
     List<PaymentAttemptModelDao> getPaymentAttempts(@Bind("paymentId") final String paymentId,
-                                                    @InternalTenantContextBinder final InternalTenantContext context);
+                                                    @BindBean final InternalTenantContext context);
 
-    @Override
-    @SqlUpdate
-    void insertHistoryFromTransaction(@PaymentAttemptHistoryBinder final EntityHistory<PaymentAttemptModelDao> payment,
-                                      @InternalTenantContextBinder final InternalCallContext context);
-
-    public static final class PaymentAttemptModelDaoBinder extends BinderBase implements Binder<Bind, PaymentAttemptModelDao> {
-
-        @Override
-        public void bind(@SuppressWarnings("rawtypes") final SQLStatement stmt, final Bind bind, final PaymentAttemptModelDao attempt) {
-            stmt.bind("id", attempt.getId().toString());
-            stmt.bind("paymentId", attempt.getPaymentId().toString());
-            stmt.bind("processingStatus", attempt.getPaymentStatus().toString());
-            stmt.bind("gatewayErrorCode", attempt.getGatewayErrorCode());
-            stmt.bind("gatewayErrorMsg", attempt.getGatewayErrorMsg());
-            stmt.bind("requestedAmount", attempt.getRequestedAmount());
-        }
-    }
 
     public static class PaymentAttemptModelDaoMapper extends MapperBase implements ResultSetMapper<PaymentAttemptModelDao> {
 
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentMethodSqlDao.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentMethodSqlDao.java
index b2cfc65..043800c 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/PaymentMethodSqlDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentMethodSqlDao.java
@@ -25,70 +25,55 @@ import org.joda.time.DateTime;
 import org.skife.jdbi.v2.SQLStatement;
 import org.skife.jdbi.v2.StatementContext;
 import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.BindBean;
 import org.skife.jdbi.v2.sqlobject.Binder;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
 import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
 import org.skife.jdbi.v2.tweak.ResultSetMapper;
 
+import com.ning.billing.payment.api.PaymentMethod;
 import com.ning.billing.util.audit.ChangeType;
 import com.ning.billing.util.callcontext.InternalCallContext;
 import com.ning.billing.util.callcontext.InternalTenantContext;
-import com.ning.billing.util.callcontext.InternalTenantContextBinder;
 import com.ning.billing.util.dao.BinderBase;
 import com.ning.billing.util.dao.EntityHistory;
 import com.ning.billing.util.dao.MapperBase;
 import com.ning.billing.util.entity.dao.Audited;
+import com.ning.billing.util.entity.dao.EntitySqlDao;
 import com.ning.billing.util.entity.dao.EntitySqlDaoStringTemplate;
-import com.ning.billing.util.entity.dao.UpdatableEntitySqlDao;
 
 @EntitySqlDaoStringTemplate
 @RegisterMapper(PaymentMethodSqlDao.PaymentMethodDaoMapper.class)
-public interface PaymentMethodSqlDao extends UpdatableEntitySqlDao<PaymentMethodModelDao> {
+public interface PaymentMethodSqlDao extends EntitySqlDao<PaymentMethod> {
 
     @SqlUpdate
     @Audited(ChangeType.INSERT)
-    void insertPaymentMethod(@Bind(binder = PaymentMethodModelDaoBinder.class) final PaymentMethodModelDao paymentMethod,
-                             @InternalTenantContextBinder final InternalCallContext context);
+    void insertPaymentMethod(@BindBean final PaymentMethodModelDao paymentMethod,
+                             @BindBean final InternalCallContext context);
 
     @SqlUpdate
     @Audited(ChangeType.UPDATE)
     void markPaymentMethodAsDeleted(@Bind("id") final String paymentMethodId,
-                                    @InternalTenantContextBinder final InternalCallContext context);
+                                    @BindBean final InternalCallContext context);
 
     @SqlUpdate
     @Audited(ChangeType.UPDATE)
     void unmarkPaymentMethodAsDeleted(@Bind("id") final String paymentMethodId,
-                                      @InternalTenantContextBinder final InternalCallContext context);
+                                      @BindBean final InternalCallContext context);
 
     @SqlQuery
     PaymentMethodModelDao getPaymentMethod(@Bind("id") final String paymentMethodId,
-                                           @InternalTenantContextBinder final InternalTenantContext context);
+                                           @BindBean final InternalTenantContext context);
 
     @SqlQuery
     PaymentMethodModelDao getPaymentMethodIncludedDelete(@Bind("id") final String paymentMethodId,
-                                                         @InternalTenantContextBinder final InternalTenantContext context);
+                                                         @BindBean final InternalTenantContext context);
 
     @SqlQuery
     List<PaymentMethodModelDao> getPaymentMethods(@Bind("accountId") final String accountId,
-                                                  @InternalTenantContextBinder final InternalTenantContext context);
+                                                  @BindBean final InternalTenantContext context);
 
-    @Override
-    @SqlUpdate
-    public void insertHistoryFromTransaction(@PaymentMethodHistoryBinder final EntityHistory<PaymentMethodModelDao> payment,
-                                             @InternalTenantContextBinder final InternalCallContext context);
-
-    public static final class PaymentMethodModelDaoBinder extends BinderBase implements Binder<Bind, PaymentMethodModelDao> {
-
-        @Override
-        public void bind(@SuppressWarnings("rawtypes") final SQLStatement stmt, final Bind bind, final PaymentMethodModelDao method) {
-            stmt.bind("id", method.getId().toString());
-            stmt.bind("accountId", method.getAccountId().toString());
-            stmt.bind("pluginName", method.getPluginName());
-            stmt.bind("isActive", method.isActive());
-            stmt.bind("externalId", method.getExternalId());
-        }
-    }
 
     public static class PaymentMethodDaoMapper extends MapperBase implements ResultSetMapper<PaymentMethodModelDao> {
 
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java
index 6f771ca..a313463 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java
@@ -26,6 +26,7 @@ import org.joda.time.DateTime;
 import org.skife.jdbi.v2.SQLStatement;
 import org.skife.jdbi.v2.StatementContext;
 import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.BindBean;
 import org.skife.jdbi.v2.sqlobject.Binder;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
@@ -42,17 +43,18 @@ import com.ning.billing.util.dao.BinderBase;
 import com.ning.billing.util.dao.EntityHistory;
 import com.ning.billing.util.dao.MapperBase;
 import com.ning.billing.util.entity.dao.Audited;
+import com.ning.billing.util.entity.dao.EntitySqlDao;
 import com.ning.billing.util.entity.dao.EntitySqlDaoStringTemplate;
 import com.ning.billing.util.entity.dao.UpdatableEntitySqlDao;
 
 @EntitySqlDaoStringTemplate
 @RegisterMapper(PaymentSqlDao.PaymentModelDaoMapper.class)
-public interface PaymentSqlDao extends UpdatableEntitySqlDao<PaymentModelDao> {
+public interface PaymentSqlDao extends EntitySqlDao<PaymentModelDao> {
 
     @SqlUpdate
     @Audited(ChangeType.INSERT)
-    void insertPayment(@Bind(binder = PaymentModelDaoBinder.class) final PaymentModelDao paymentInfo,
-                       @InternalTenantContextBinder final InternalCallContext context);
+    void insertPayment(@BindBean final PaymentModelDao paymentInfo,
+                       @BindBean final InternalCallContext context);
 
     @SqlUpdate
     @Audited(ChangeType.UPDATE)
@@ -60,52 +62,32 @@ public interface PaymentSqlDao extends UpdatableEntitySqlDao<PaymentModelDao> {
                                       @Bind("paymentStatus") final String paymentStatus,
                                       @Bind("extFirstPaymentRefId") final String extFirstPaymentRefId,
                                       @Bind("extSecondPaymentRefId") final String extSecondPaymentRefId,
-                                      @InternalTenantContextBinder final InternalCallContext context);
+                                      @BindBean final InternalCallContext context);
 
     @SqlUpdate
     @Audited(ChangeType.UPDATE)
     void updatePaymentAmount(@Bind("id") final String paymentId,
                              @Bind("amount") final BigDecimal amount,
-                             @InternalTenantContextBinder final InternalCallContext context);
+                             @BindBean final InternalCallContext context);
 
     @SqlQuery
     PaymentModelDao getPayment(@Bind("id") final String paymentId,
-                               @InternalTenantContextBinder final InternalTenantContext context);
+                               @BindBean final InternalTenantContext context);
 
     @SqlQuery
     PaymentModelDao getLastPaymentForAccountAndPaymentMethod(@Bind("accountId") final String accountId,
                                                              @Bind("paymentMethodId") final String paymentMethodId,
-                                                             @InternalTenantContextBinder final InternalTenantContext context);
+                                                             @BindBean final InternalTenantContext context);
 
     @SqlQuery
     List<PaymentModelDao> getPaymentsForInvoice(@Bind("invoiceId") final String invoiceId,
-                                                @InternalTenantContextBinder final InternalTenantContext context);
+                                                @BindBean final InternalTenantContext context);
 
     @SqlQuery
     List<PaymentModelDao> getPaymentsForAccount(@Bind("accountId") final String accountId,
-                                                @InternalTenantContextBinder final InternalTenantContext context);
+                                                @BindBean final InternalTenantContext context);
 
-    @Override
-    @SqlUpdate
-    void insertHistoryFromTransaction(@PaymentHistoryBinder final EntityHistory<PaymentModelDao> payment,
-                                      @InternalTenantContextBinder final InternalCallContext context);
-
-    public static final class PaymentModelDaoBinder extends BinderBase implements Binder<Bind, PaymentModelDao> {
 
-        @Override
-        public void bind(@SuppressWarnings("rawtypes") final SQLStatement stmt, final Bind bind, final PaymentModelDao payment) {
-            stmt.bind("id", payment.getId().toString());
-            stmt.bind("accountId", payment.getAccountId().toString());
-            stmt.bind("invoiceId", payment.getInvoiceId().toString());
-            stmt.bind("paymentMethodId", payment.getPaymentMethodId().toString());
-            stmt.bind("amount", payment.getAmount());
-            stmt.bind("currency", payment.getCurrency().toString());
-            stmt.bind("effectiveDate", getDate(payment.getEffectiveDate()));
-            stmt.bind("paymentStatus", payment.getPaymentStatus().toString());
-            stmt.bind("extFirstPaymentRefId", payment.getExtFirstPaymentRefId());
-            stmt.bind("extSecondPaymentRefId", payment.getExtSecondPaymentRefId());
-        }
-    }
 
     public static class PaymentModelDaoMapper extends MapperBase implements ResultSetMapper<PaymentModelDao> {
 
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/RefundSqlDao.java b/payment/src/main/java/com/ning/billing/payment/dao/RefundSqlDao.java
index c2c4f01..efa4b56 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/RefundSqlDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/RefundSqlDao.java
@@ -23,10 +23,9 @@ import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
-import org.skife.jdbi.v2.SQLStatement;
 import org.skife.jdbi.v2.StatementContext;
 import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BindBean;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
 import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
@@ -37,60 +36,38 @@ import com.ning.billing.payment.dao.RefundModelDao.RefundStatus;
 import com.ning.billing.util.audit.ChangeType;
 import com.ning.billing.util.callcontext.InternalCallContext;
 import com.ning.billing.util.callcontext.InternalTenantContext;
-import com.ning.billing.util.callcontext.InternalTenantContextBinder;
-import com.ning.billing.util.dao.BinderBase;
-import com.ning.billing.util.dao.EntityHistory;
 import com.ning.billing.util.dao.MapperBase;
 import com.ning.billing.util.entity.dao.Audited;
+import com.ning.billing.util.entity.dao.EntitySqlDao;
 import com.ning.billing.util.entity.dao.EntitySqlDaoStringTemplate;
-import com.ning.billing.util.entity.dao.UpdatableEntitySqlDao;
 
 @EntitySqlDaoStringTemplate
 @RegisterMapper(RefundSqlDao.RefundModelDaoMapper.class)
-public interface RefundSqlDao extends UpdatableEntitySqlDao<RefundModelDao> {
+public interface RefundSqlDao extends EntitySqlDao<RefundModelDao> {
 
     @SqlUpdate
     @Audited(ChangeType.INSERT)
-    void insertRefund(@Bind(binder = RefundModelDaoBinder.class) final RefundModelDao refundInfo,
-                      @InternalTenantContextBinder final InternalCallContext context);
+    void insertRefund(@BindBean final RefundModelDao refundInfo,
+                      @BindBean final InternalCallContext context);
 
     @SqlUpdate
     @Audited(ChangeType.UPDATE)
     void updateStatus(@Bind("id") final String refundId,
                       @Bind("refundStatus") final String status,
-                      @InternalTenantContextBinder final InternalCallContext context);
+                      @BindBean final InternalCallContext context);
 
     @SqlQuery
     RefundModelDao getRefund(@Bind("id") final String refundId,
-                             @InternalTenantContextBinder final InternalTenantContext context);
+                             @BindBean final InternalTenantContext context);
 
     @SqlQuery
     List<RefundModelDao> getRefundsForPayment(@Bind("paymentId") final String paymentId,
-                                              @InternalTenantContextBinder final InternalTenantContext context);
+                                              @BindBean final InternalTenantContext context);
 
     @SqlQuery
     List<RefundModelDao> getRefundsForAccount(@Bind("accountId") final String accountId,
-                                              @InternalTenantContextBinder final InternalTenantContext context);
+                                              @BindBean final InternalTenantContext context);
 
-    @Override
-    @SqlUpdate
-    public void insertHistoryFromTransaction(@RefundHistoryBinder final EntityHistory<RefundModelDao> payment,
-                                             @InternalTenantContextBinder final InternalCallContext context);
-
-    public static final class RefundModelDaoBinder extends BinderBase implements Binder<Bind, RefundModelDao> {
-
-        @Override
-        public void bind(final SQLStatement stmt, final Bind bind, final RefundModelDao refund) {
-            stmt.bind("id", refund.getId().toString());
-            stmt.bind("accountId", refund.getAccountId().toString());
-            stmt.bind("paymentId", refund.getPaymentId().toString());
-            stmt.bind("amount", refund.getAmount());
-            stmt.bind("currency", refund.getCurrency().toString());
-            stmt.bind("isAdjusted", refund.isAdjsuted());
-            stmt.bind("refundStatus", refund.getRefundStatus().toString());
-            // createdDate and updatedDate are populated by the @InternalTenantContextBinder
-        }
-    }
 
     public static class RefundModelDaoMapper extends MapperBase implements ResultSetMapper<RefundModelDao> {
 
diff --git a/payment/src/main/resources/com/ning/billing/payment/ddl.sql b/payment/src/main/resources/com/ning/billing/payment/ddl.sql
index dc71838..d9dd685 100644
--- a/payment/src/main/resources/com/ning/billing/payment/ddl.sql
+++ b/payment/src/main/resources/com/ning/billing/payment/ddl.sql
@@ -26,9 +26,9 @@ CREATE INDEX payments_tenant_account_record_id ON payments(tenant_record_id, acc
 
 DROP TABLE IF EXISTS payment_history; 
 CREATE TABLE payment_history (
-    history_record_id int(11) unsigned NOT NULL AUTO_INCREMENT,    
-    record_id int(11) unsigned NOT NULL,
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
     id char(36) NOT NULL,
+    target_record_id int(11) unsigned NOT NULL,
     account_id char(36) COLLATE utf8_bin NOT NULL,
     invoice_id char(36) COLLATE utf8_bin NOT NULL,
     payment_method_id char(36) COLLATE utf8_bin NOT NULL,    
@@ -38,15 +38,16 @@ CREATE TABLE payment_history (
     payment_status varchar(50),
     ext_first_payment_ref_id varchar(128),
     ext_second_payment_ref_id varchar(128),    
+    change_type char(6) NOT NULL,
     created_by varchar(50) NOT NULL,
     created_date datetime NOT NULL,
     updated_by varchar(50) NOT NULL,
     updated_date datetime NOT NULL,
     account_record_id int(11) unsigned default null,
     tenant_record_id int(11) unsigned default null,
-    PRIMARY KEY (history_record_id)
+    PRIMARY KEY(record_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-CREATE INDEX payment_history_record_id ON payment_history(record_id);
+CREATE INDEX payment_history_target_record_id ON payment_history(target_record_id);
 CREATE INDEX payment_history_tenant_account_record_id ON payment_history(tenant_record_id, account_record_id);
 
 DROP TABLE IF EXISTS payment_attempts;
@@ -72,23 +73,24 @@ CREATE INDEX payment_attempts_tenant_account_record_id ON payment_attempts(tenan
 
 DROP TABLE IF EXISTS payment_attempt_history;
 CREATE TABLE payment_attempt_history (
-    history_record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
-    record_id int(11) unsigned NOT NULL,
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
     id char(36) NOT NULL,
+    target_record_id int(11) unsigned NOT NULL,
     payment_id char(36) COLLATE utf8_bin NOT NULL,
     gateway_error_code varchar(32),              
     gateway_error_msg varchar(256),
     processing_status varchar(50),
     requested_amount numeric(10,4),
+    change_type char(6) NOT NULL,
     created_by varchar(50) NOT NULL,
     created_date datetime NOT NULL,
     updated_by varchar(50) NOT NULL,
     updated_date datetime NOT NULL,
     account_record_id int(11) unsigned default null,
     tenant_record_id int(11) unsigned default null,
-    PRIMARY KEY (history_record_id)
+    PRIMARY KEY(record_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-CREATE INDEX payment_attempt_history_record_id ON payment_attempt_history(record_id);
+CREATE INDEX payment_attempt_history_target_record_id ON payment_attempt_history(target_record_id);
 CREATE INDEX payment_attempt_history_tenant_account_record_id ON payment_attempt_history(tenant_record_id, account_record_id);
 
 DROP TABLE IF EXISTS payment_methods;
@@ -113,22 +115,23 @@ CREATE INDEX payment_methods_tenant_account_record_id ON payment_methods(tenant_
 
 DROP TABLE IF EXISTS payment_method_history;
 CREATE TABLE payment_method_history (
-    history_record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
-    record_id int(11) unsigned NOT NULL,
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
     id char(36) NOT NULL,
+    target_record_id int(11) unsigned NOT NULL,
     account_id char(36) COLLATE utf8_bin NOT NULL,
     plugin_name varchar(20) DEFAULT NULL, 
     is_active bool DEFAULT true, 
     external_id varchar(64),                  
+    change_type char(6) NOT NULL,
     created_by varchar(50) NOT NULL,
     created_date datetime NOT NULL,
     updated_by varchar(50) NOT NULL,
     updated_date datetime NOT NULL,
     account_record_id int(11) unsigned default null,
     tenant_record_id int(11) unsigned default null,
-    PRIMARY KEY (history_record_id)
+    PRIMARY KEY(record_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-CREATE UNIQUE INDEX payment_method_history_record_id ON payment_method_history(record_id);
+CREATE UNIQUE INDEX payment_method_history_target_record_id ON payment_method_history(target_record_id);
 CREATE INDEX payment_method_history_tenant_account_record_id ON payment_method_history(tenant_record_id, account_record_id);
 
 DROP TABLE IF EXISTS refunds; 
@@ -156,24 +159,25 @@ CREATE INDEX refunds_tenant_account_record_id ON refunds(tenant_record_id, accou
 
 DROP TABLE IF EXISTS refund_history; 
 CREATE TABLE refund_history (
-    history_record_id int(11) unsigned NOT NULL AUTO_INCREMENT, 
-    record_id int(11) unsigned NOT NULL,
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
     id char(36) NOT NULL,
+    target_record_id int(11) unsigned NOT NULL,
     account_id char(36) COLLATE utf8_bin NOT NULL,
     payment_id char(36) COLLATE utf8_bin NOT NULL,    
     amount numeric(10,4),
     currency char(3),   
     is_adjusted tinyint(1),
     refund_status varchar(50), 
+    change_type char(6) NOT NULL,
     created_by varchar(50) NOT NULL,
     created_date datetime NOT NULL,
     updated_by varchar(50) NOT NULL,
     updated_date datetime NOT NULL,
     account_record_id int(11) unsigned default null,
     tenant_record_id int(11) unsigned default null,
-    PRIMARY KEY (history_record_id)
+    PRIMARY KEY(record_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-CREATE INDEX refund_history_record_id ON refund_history(record_id);
+CREATE INDEX refund_history_target_record_id ON refund_history(target_record_id);
 CREATE INDEX refund_history_tenant_account_record_id ON refund_history(tenant_record_id, account_record_id);
 
 
diff --git a/tenant/src/main/java/com/ning/billing/tenant/dao/TenantKVSqlDao.java b/tenant/src/main/java/com/ning/billing/tenant/dao/TenantKVSqlDao.java
index ae58d87..d6d2772 100644
--- a/tenant/src/main/java/com/ning/billing/tenant/dao/TenantKVSqlDao.java
+++ b/tenant/src/main/java/com/ning/billing/tenant/dao/TenantKVSqlDao.java
@@ -23,6 +23,7 @@ import java.util.UUID;
 import org.joda.time.DateTime;
 import org.skife.jdbi.v2.StatementContext;
 import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.BindBean;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
 import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
@@ -37,16 +38,20 @@ import com.ning.billing.util.callcontext.InternalCallContext;
 import com.ning.billing.util.callcontext.InternalTenantContextBinder;
 import com.ning.billing.util.dao.MapperBase;
 import com.ning.billing.util.dao.UuidMapper;
+import com.ning.billing.util.entity.dao.EntitySqlDao;
+import com.ning.billing.util.entity.dao.EntitySqlDaoStringTemplate;
 
-@ExternalizedSqlViaStringTemplate3
+@EntitySqlDaoStringTemplate
 @RegisterMapper({UuidMapper.class, TenantKVMapper.class})
-public interface TenantKVSqlDao extends Transactional<TenantKVSqlDao> {
+public interface TenantKVSqlDao extends EntitySqlDao<TenantKV> {
+
+    // TODO should take a context
 
     @SqlQuery
     public List<TenantKV> getTenantValueForKey(@Bind("key") final String key, @Bind("tenantRecordId") Long tenantRecordId);
 
     @SqlUpdate
-    public void insertTenantKeyValue(@Bind("id") String id, @Bind("key") final String key, @Bind("value") final String value, @Bind("tenantRecordId") Long tenantRecordId, @InternalTenantContextBinder final InternalCallContext context);
+    public void insertTenantKeyValue(@Bind("id") String id, @Bind("key") final String key, @Bind("value") final String value, @Bind("tenantRecordId") Long tenantRecordId, @BindBean final InternalCallContext context);
 
     @SqlUpdate
     public void deleteTenantKey(@Bind("key") final String key, @Bind("tenantRecordId") Long tenantRecordId);
diff --git a/util/src/main/java/com/ning/billing/util/customfield/dao/AuditedCustomFieldDao.java b/util/src/main/java/com/ning/billing/util/customfield/dao/AuditedCustomFieldDao.java
index decb30a..eb3ea19 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/dao/AuditedCustomFieldDao.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/dao/AuditedCustomFieldDao.java
@@ -40,7 +40,7 @@ public class AuditedCustomFieldDao extends EntityDaoBase<CustomField, CustomFiel
 
     @Inject
     public AuditedCustomFieldDao(final IDBI dbi) {
-        super(new EntitySqlDaoTransactionalJdbiWrapper(dbi));
+        super(new EntitySqlDaoTransactionalJdbiWrapper(dbi), CustomFieldSqlDao.class);
     }
 
     @Override
diff --git a/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldBinder.java b/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldBinder.java
index 6c20dd7..95c5624 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldBinder.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldBinder.java
@@ -40,8 +40,8 @@ public @interface CustomFieldBinder {
                 @Override
                 public void bind(final SQLStatement q, final CustomFieldBinder bind, final CustomField customField) {
                     q.bind("id", customField.getId().toString());
-                    q.bind("fieldName", customField.getName());
-                    q.bind("fieldValue", customField.getValue());
+                    q.bind("fieldName", customField.getFieldName());
+                    q.bind("fieldValue", customField.getFieldValue());
                 }
             };
         }
diff --git a/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldHistoryBinder.java b/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldHistoryBinder.java
index 48b53dd..c66a268 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldHistoryBinder.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldHistoryBinder.java
@@ -43,8 +43,8 @@ public @interface CustomFieldHistoryBinder {
 //                    q.bind("recordId", customFieldHistory.getValue());
 //                    q.bind("changeType", customFieldHistory.getChangeType().toString());
                     q.bind("id", customFieldHistory.getId().toString());
-                    q.bind("fieldName", customFieldHistory.getEntity().getName());
-                    q.bind("fieldValue", customFieldHistory.getEntity().getValue());
+                    q.bind("fieldName", customFieldHistory.getEntity().getFieldName());
+                    q.bind("fieldValue", customFieldHistory.getEntity().getFieldValue());
                 }
             };
         }
diff --git a/util/src/main/java/com/ning/billing/util/customfield/StringCustomField.java b/util/src/main/java/com/ning/billing/util/customfield/StringCustomField.java
index 179c706..e820c00 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/StringCustomField.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/StringCustomField.java
@@ -27,8 +27,8 @@ import com.ning.billing.util.entity.EntityBase;
 public class StringCustomField extends EntityBase implements CustomField {
 
 
-    private final String name;
-    private String value;
+    private final String fieldName;
+    private final String fieldValue;
     private final UUID objectId;
     private final ObjectType objectType;
 
@@ -38,21 +38,21 @@ public class StringCustomField extends EntityBase implements CustomField {
 
     public StringCustomField(final UUID id, final String name, final String value, final ObjectType objectType, final UUID objectId, final DateTime createdDate) {
         super(id, createdDate, createdDate);
-        this.name = name;
-        this.value = value;
+        this.fieldName = name;
+        this.fieldValue = value;
         this.objectId = objectId;
         this.objectType = objectType;
 
     }
 
     @Override
-    public String getName() {
-        return name;
+    public String getFieldName() {
+        return fieldName;
     }
 
     @Override
-    public String getValue() {
-        return value;
+    public String getFieldValue() {
+        return fieldValue;
     }
 
     public ObjectType getObjectType() {
@@ -64,51 +64,54 @@ public class StringCustomField extends EntityBase implements CustomField {
     }
 
     @Override
-    public void setValue(final String value) {
-        this.value = value;
-    }
-
-    @Override
     public String toString() {
-        return "StringCustomField [name=" + name + ", value=" + value + ", id=" + id + "]";
-    }
-
-    @Override
-    public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result + ((name == null) ? 0 : name.hashCode());
-        result = prime * result + ((value == null) ? 0 : value.hashCode());
-        return result;
+        final StringBuilder sb = new StringBuilder();
+        sb.append("StringCustomField");
+        sb.append("{fieldName='").append(fieldName).append('\'');
+        sb.append(", fieldValue='").append(fieldValue).append('\'');
+        sb.append(", objectId=").append(objectId);
+        sb.append(", objectType=").append(objectType);
+        sb.append('}');
+        return sb.toString();
     }
 
     @Override
-    public boolean equals(final Object obj) {
-        if (this == obj) {
+    public boolean equals(final Object o) {
+        if (this == o) {
             return true;
         }
-        if (obj == null) {
+        if (o == null || getClass() != o.getClass()) {
             return false;
         }
-        if (getClass() != obj.getClass()) {
+        if (!super.equals(o)) {
             return false;
         }
-        final StringCustomField other = (StringCustomField) obj;
-        if (name == null) {
-            if (other.name != null) {
-                return false;
-            }
-        } else if (!name.equals(other.name)) {
+
+        final StringCustomField that = (StringCustomField) o;
+
+        if (fieldName != null ? !fieldName.equals(that.fieldName) : that.fieldName != null) {
             return false;
         }
-        if (value == null) {
-            if (other.value != null) {
-                return false;
-            }
-        } else if (!value.equals(other.value)) {
+        if (fieldValue != null ? !fieldValue.equals(that.fieldValue) : that.fieldValue != null) {
             return false;
         }
+        if (objectId != null ? !objectId.equals(that.objectId) : that.objectId != null) {
+            return false;
+        }
+        if (objectType != that.objectType) {
+            return false;
+        }
+
         return true;
     }
 
+    @Override
+    public int hashCode() {
+        int result = super.hashCode();
+        result = 31 * result + (fieldName != null ? fieldName.hashCode() : 0);
+        result = 31 * result + (fieldValue != null ? fieldValue.hashCode() : 0);
+        result = 31 * result + (objectId != null ? objectId.hashCode() : 0);
+        result = 31 * result + (objectType != null ? objectType.hashCode() : 0);
+        return result;
+    }
 }
diff --git a/util/src/main/java/com/ning/billing/util/dao/CollectionHistorySqlDao.java b/util/src/main/java/com/ning/billing/util/dao/CollectionHistorySqlDao.java
index 75b7160..2b498db 100644
--- a/util/src/main/java/com/ning/billing/util/dao/CollectionHistorySqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/dao/CollectionHistorySqlDao.java
@@ -32,9 +32,4 @@ public interface CollectionHistorySqlDao<T extends Entity> {
     public void addHistoryFromTransaction(String objectId, ObjectType objectType,
                                           List<EntityHistory<T>> histories,
                                           @InternalTenantContextBinder InternalCallContext context);
-
-    @SqlUpdate
-    public void addHistoryFromTransaction(String objectId, ObjectType objectType,
-                                          EntityHistory<T> history,
-                                          @InternalTenantContextBinder InternalCallContext context);
 }
diff --git a/util/src/main/java/com/ning/billing/util/entity/dao/EntityDaoBase.java b/util/src/main/java/com/ning/billing/util/entity/dao/EntityDaoBase.java
index e38bb40..2a1c085 100644
--- a/util/src/main/java/com/ning/billing/util/entity/dao/EntityDaoBase.java
+++ b/util/src/main/java/com/ning/billing/util/entity/dao/EntityDaoBase.java
@@ -30,8 +30,11 @@ public abstract class EntityDaoBase<T extends Entity, U extends BillingException
 
     protected final EntitySqlDaoTransactionalJdbiWrapper transactionalSqlDao;
 
-    public EntityDaoBase(final EntitySqlDaoTransactionalJdbiWrapper transactionalSqlDao) {
+    private Class<? extends EntitySqlDao<T>> realSqlDao;
+
+    public EntityDaoBase(final EntitySqlDaoTransactionalJdbiWrapper transactionalSqlDao, Class<? extends EntitySqlDao<T>> realSqlDao) {
         this.transactionalSqlDao = transactionalSqlDao;
+        this.realSqlDao = realSqlDao;
     }
 
     @Override
@@ -43,7 +46,7 @@ public abstract class EntityDaoBase<T extends Entity, U extends BillingException
                 if (getById(entity.getId(), context) != null) {
                     throw generateAlreadyExistsException(entity, context);
                 }
-                final EntitySqlDao<T> transactional = entitySqlDaoWrapperFactory.become(EntitySqlDao.class);
+                final EntitySqlDao<T> transactional = entitySqlDaoWrapperFactory.become(realSqlDao);
                 transactional.create(entity, context);
 
                 postBusEventFromTransaction(entity, entity, ChangeType.INSERT, entitySqlDaoWrapperFactory, context);
@@ -64,7 +67,8 @@ public abstract class EntityDaoBase<T extends Entity, U extends BillingException
 
             @Override
             public Long inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                return entitySqlDaoWrapperFactory.become(EntitySqlDao.class).getRecordId(id.toString(), context);
+                final EntitySqlDao<T> transactional = entitySqlDaoWrapperFactory.become(realSqlDao);
+                return transactional.getRecordId(id.toString(), context);
             }
         });
     }
@@ -75,7 +79,8 @@ public abstract class EntityDaoBase<T extends Entity, U extends BillingException
 
             @Override
             public T inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                return (T) entitySqlDaoWrapperFactory.become(EntitySqlDao.class).getByRecordId(recordId, context);
+                final EntitySqlDao<T> transactional = entitySqlDaoWrapperFactory.become(realSqlDao);
+                return transactional.getByRecordId(recordId, context);
             }
         });
     }
@@ -86,7 +91,8 @@ public abstract class EntityDaoBase<T extends Entity, U extends BillingException
 
             @Override
             public T inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                return (T) entitySqlDaoWrapperFactory.become(EntitySqlDao.class).getById(id.toString(), context);
+                final EntitySqlDao<T> transactional = entitySqlDaoWrapperFactory.become(realSqlDao);
+                return transactional.getById(id.toString(), context);
             }
         });
     }
@@ -97,7 +103,8 @@ public abstract class EntityDaoBase<T extends Entity, U extends BillingException
 
             @Override
             public List<T> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                return entitySqlDaoWrapperFactory.become(EntitySqlDao.class).get(context);
+                final EntitySqlDao<T> transactional = entitySqlDaoWrapperFactory.become(realSqlDao);
+                return transactional.get(context);
             }
         });
     }
@@ -108,7 +115,8 @@ public abstract class EntityDaoBase<T extends Entity, U extends BillingException
 
             @Override
             public Void inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                entitySqlDaoWrapperFactory.become(EntitySqlDao.class).test(context);
+                final EntitySqlDao<T> transactional = entitySqlDaoWrapperFactory.become(realSqlDao);
+                transactional.test(context);
                 return null;
             }
         });
diff --git a/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDao.java b/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDao.java
index fa99851..b360462 100644
--- a/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDao.java
@@ -45,23 +45,23 @@ public interface EntitySqlDao<T extends Entity> extends AuditSqlDao, HistorySqlD
 
     @SqlQuery
     public T getById(@Bind("id") final String id,
-                     @InternalTenantContextBinder final InternalTenantContext context);
+                     @BindBean final InternalTenantContext context);
 
     @SqlQuery
     public T getByRecordId(@Bind("recordId") final Long recordId,
-                           @InternalTenantContextBinder final InternalTenantContext context);
+                           @BindBean final InternalTenantContext context);
 
     @SqlQuery
     public Long getRecordId(@Bind("id") final String id,
-                            @InternalTenantContextBinder final InternalTenantContext context);
+                            @BindBean final InternalTenantContext context);
 
     @SqlQuery
     public Long getHistoryRecordId(@Bind("recordId") final Long recordId,
-                                   @InternalTenantContextBinder final InternalTenantContext context);
+                                   @BindBean final InternalTenantContext context);
 
     @SqlQuery
-    public List<T> get(@InternalTenantContextBinder final InternalTenantContext context);
+    public List<T> get(@BindBean final InternalTenantContext context);
 
     @SqlUpdate
-    public void test(@InternalTenantContextBinder final InternalTenantContext context);
+    public void test(@BindBean final InternalTenantContext context);
 }
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/AuditedTagDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/AuditedTagDao.java
index 1cec626..990fbcd 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/AuditedTagDao.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/AuditedTagDao.java
@@ -61,7 +61,7 @@ public class AuditedTagDao extends EntityDaoBase<Tag, TagApiException> implement
 
     @Inject
     public AuditedTagDao(final IDBI dbi, final TagEventBuilder tagEventBuilder, final InternalBus bus, final Clock clock) {
-        super(new EntitySqlDaoTransactionalJdbiWrapper(dbi));
+        super(new EntitySqlDaoTransactionalJdbiWrapper(dbi), TagSqlDao.class);
         this.clock = clock;
         this.tagEventBuilder = tagEventBuilder;
         this.bus = bus;
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/DefaultTagDefinitionDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/DefaultTagDefinitionDao.java
index e2a3ad5..7a56736 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/DefaultTagDefinitionDao.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/DefaultTagDefinitionDao.java
@@ -59,7 +59,7 @@ public class DefaultTagDefinitionDao extends EntityDaoBase<TagDefinition, TagDef
 
     @Inject
     public DefaultTagDefinitionDao(final IDBI dbi, final TagEventBuilder tagEventBuilder, final InternalBus bus) {
-        super(new EntitySqlDaoTransactionalJdbiWrapper(dbi));
+        super(new EntitySqlDaoTransactionalJdbiWrapper(dbi), TagDefinitionSqlDao.class);
         this.tagEventBuilder = tagEventBuilder;
         this.bus = bus;
         this.tagDefinitionSqlDao = dbi.onDemand(TagDefinitionSqlDao.class);
diff --git a/util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg
index 018104e..d73eaf4 100644
--- a/util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg
@@ -5,6 +5,8 @@ tableName() ::= "custom_fields"
 tableFields(prefix) ::= <<
   <prefix>object_id
 , <prefix>object_type
+, <prefix>field_name
+, <prefix>field_value
 , <prefix>created_by
 , <prefix>created_date
 , <prefix>updated_by
@@ -14,6 +16,8 @@ tableFields(prefix) ::= <<
 tableValues() ::= <<
   :objectId
 , :objectType
+, :fieldName
+, :fieldValue
 , :createdBy
 , :createdDate
 , :updatedBy
diff --git a/util/src/main/resources/com/ning/billing/util/ddl.sql b/util/src/main/resources/com/ning/billing/util/ddl.sql
index 11af132..2a1edb7 100644
--- a/util/src/main/resources/com/ning/billing/util/ddl.sql
+++ b/util/src/main/resources/com/ning/billing/util/ddl.sql
@@ -21,21 +21,23 @@ CREATE INDEX custom_fields_tenant_account_record_id ON custom_fields(tenant_reco
 
 DROP TABLE IF EXISTS custom_field_history;
 CREATE TABLE custom_field_history (
-    history_record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
-    record_id int(11) unsigned NOT NULL,
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
     id char(36) NOT NULL,
+    target_record_id int(11) unsigned NOT NULL,
     object_id char(36) NOT NULL,
     object_type varchar(30) NOT NULL,
     field_name varchar(30),
     field_value varchar(255),
-    updated_by varchar(50) NOT NULL,
-    date datetime NOT NULL,
     change_type char(6) NOT NULL,
+    created_by varchar(50) NOT NULL,
+    created_date datetime NOT NULL,
+    updated_by varchar(50) NOT NULL,
+    updated_date datetime NOT NULL,
     account_record_id int(11) unsigned default null,
     tenant_record_id int(11) unsigned default null,
-    PRIMARY KEY(history_record_id)
+    PRIMARY KEY(record_id)
 ) ENGINE=innodb;
-CREATE INDEX custom_field_history_record_id ON custom_field_history(record_id);
+CREATE INDEX custom_field_history_target_record_id ON custom_field_history(target_record_id);
 CREATE INDEX custom_field_history_object_id_object_type ON custom_fields(object_id, object_type);
 CREATE INDEX custom_field_history_tenant_account_record_id ON custom_field_history(tenant_record_id, account_record_id);
 
@@ -64,10 +66,11 @@ CREATE TABLE tag_definition_history (
     name varchar(30) NOT NULL,
     description varchar(200),
     change_type char(6) NOT NULL,
-    created_by varchar(50),
+    created_by varchar(50) NOT NULL,
     created_date datetime NOT NULL,
     updated_by varchar(50) NOT NULL,
     updated_date datetime NOT NULL,
+    account_record_id int(11) unsigned default null,
     tenant_record_id int(11) unsigned default null,
     PRIMARY KEY(record_id)
 ) ENGINE=innodb;
@@ -105,10 +108,10 @@ CREATE TABLE tag_history (
     object_type varchar(30) NOT NULL,
     tag_definition_id char(36) NOT NULL,
     change_type char(6) NOT NULL,
-    created_date datetime NOT NULL,
     created_by varchar(50) NOT NULL,
-    updated_date datetime NOT NULL,
+    created_date datetime NOT NULL,
     updated_by varchar(50) NOT NULL,
+    updated_date datetime NOT NULL,
     account_record_id int(11) unsigned default null,
     tenant_record_id int(11) unsigned default null,
     PRIMARY KEY(record_id)