killbill-aplcache

account email stack

4/17/2012 5:08:09 PM

Changes

Details

diff --git a/account/src/main/java/com/ning/billing/account/api/DefaultAccountEmail.java b/account/src/main/java/com/ning/billing/account/api/DefaultAccountEmail.java
new file mode 100644
index 0000000..dd9323d
--- /dev/null
+++ b/account/src/main/java/com/ning/billing/account/api/DefaultAccountEmail.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.account.api;
+
+import com.ning.billing.util.entity.UpdatableEntityBase;
+import org.joda.time.DateTime;
+
+import java.util.UUID;
+
+public class DefaultAccountEmail extends UpdatableEntityBase implements AccountEmail {
+    private final UUID accountId;
+    private final String email;
+
+    public DefaultAccountEmail(UUID accountId, String email) {
+        super();
+        this.accountId = accountId;
+        this.email = email;
+    }
+
+    public DefaultAccountEmail(AccountEmail source, String newEmail) {
+        this(source.getId(), source.getAccountId(), newEmail,
+             source.getCreatedBy(), source.getCreatedDate(), source.getUpdatedBy(), source.getUpdatedDate());
+    }
+
+    public DefaultAccountEmail(UUID id, UUID accountId, String email, String createdBy, DateTime createdDate, String updatedBy, DateTime updatedDate) {
+        super(id, createdBy, createdDate, updatedBy, updatedDate);
+        this.accountId = accountId;
+        this.email = email;
+    }
+
+    @Override
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    @Override
+    public String getEmail() {
+        return email;
+    }
+}
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 37f5322..ad186fd 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
@@ -16,6 +16,9 @@
 
 package com.ning.billing.account.api.user;
 
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 import java.util.UUID;
 
@@ -24,9 +27,9 @@ import com.ning.billing.ErrorCode;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.account.api.AccountData;
+import com.ning.billing.account.api.AccountEmail;
 import com.ning.billing.account.api.DefaultAccount;
 import com.ning.billing.account.api.MigrationAccountData;
-import com.ning.billing.account.api.MutableAccountData;
 import com.ning.billing.account.dao.AccountDao;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.callcontext.CallContextFactory;
@@ -100,7 +103,6 @@ public class DefaultAccountUserApi implements com.ning.billing.account.api.Accou
         } catch (EntityPersistenceException e) {
             throw new AccountApiException(e, e.getCode(), e.getMessage());
         }
-  
     }
 
     @Override
@@ -132,5 +134,13 @@ public class DefaultAccountUserApi implements com.ning.billing.account.api.Accou
         return account;
 	}
 
+    @Override
+    public List<AccountEmail> getEmails(final UUID accountId) {
+        return dao.getEmails(accountId);
+    }
 
+    @Override
+    public void saveEmails(final UUID accountId, final List<AccountEmail> newEmails, final CallContext context) {
+        dao.saveEmails(accountId, newEmails, context);
+    }
 }
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountDao.java b/account/src/main/java/com/ning/billing/account/dao/AccountDao.java
index 4be1058..00f785f 100644
--- a/account/src/main/java/com/ning/billing/account/dao/AccountDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountDao.java
@@ -16,12 +16,16 @@
 
 package com.ning.billing.account.dao;
 
+import java.util.List;
 import java.util.UUID;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.account.api.AccountEmail;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.entity.UpdatableEntityDao;
 
+import javax.annotation.Nullable;
+
 public interface AccountDao extends UpdatableEntityDao<Account> {
     public Account getAccountByKey(String key);
 
@@ -32,4 +36,8 @@ public interface AccountDao extends UpdatableEntityDao<Account> {
      * @throws AccountApiException when externalKey is null
      */
     public UUID getIdFromKey(String externalKey) throws AccountApiException;
+
+    public List<AccountEmail> getEmails(UUID accountId);
+
+    public void saveEmails(UUID accountId, List<AccountEmail> emails, CallContext context);
 }
\ No newline at end of file
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountEmailBinder.java b/account/src/main/java/com/ning/billing/account/dao/AccountEmailBinder.java
new file mode 100644
index 0000000..bd71364
--- /dev/null
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountEmailBinder.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.account.dao;
+
+import com.ning.billing.account.api.AccountEmail;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+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;
+
+@BindingAnnotation(AccountEmailBinder.AccountEmailBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface AccountEmailBinder {
+    public static class AccountEmailBinderFactory implements BinderFactory {
+        @Override
+        public Binder<AccountEmailBinder, AccountEmail> build(Annotation annotation) {
+            return new Binder<AccountEmailBinder, AccountEmail>() {
+                @Override
+                public void bind(@SuppressWarnings("rawtypes") SQLStatement q, AccountEmailBinder bind, AccountEmail accountEmail) {
+                    q.bind("id", accountEmail.getId().toString());
+                    q.bind("accountId", accountEmail.getAccountId().toString());
+                    q.bind("email", accountEmail.getEmail());
+                }
+            };
+        }
+    }
+}
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountEmailMapper.java b/account/src/main/java/com/ning/billing/account/dao/AccountEmailMapper.java
new file mode 100644
index 0000000..9444941
--- /dev/null
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountEmailMapper.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.account.dao;
+
+import com.ning.billing.account.api.AccountEmail;
+import com.ning.billing.account.api.DefaultAccountEmail;
+import com.ning.billing.util.dao.MapperBase;
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+public class AccountEmailMapper extends MapperBase implements ResultSetMapper<AccountEmail> {
+    @Override
+    public AccountEmail map(int index, ResultSet result, StatementContext context) throws SQLException {
+        UUID id = UUID.fromString(result.getString("id"));
+        UUID accountId = UUID.fromString(result.getString("account_id"));
+        String email = result.getString("email");
+
+        String createdBy = result.getString("created_by");
+        DateTime createdDate = getDate(result, "created_date");
+        String updatedBy = result.getString("updated_by");
+        DateTime updatedDate = getDate(result, "updated_date");
+
+        return new DefaultAccountEmail(id, accountId, email, createdBy, createdDate, updatedBy, updatedDate);
+    }
+}
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
new file mode 100644
index 0000000..1902154
--- /dev/null
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountEmailSqlDao.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.account.dao;
+
+import com.ning.billing.account.api.AccountEmail;
+import com.ning.billing.util.ChangeType;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextBinder;
+import com.ning.billing.util.dao.ChangeTypeBinder;
+import com.ning.billing.util.entity.UpdatableEntityDao;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlBatch;
+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 org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+
+import java.util.List;
+
+@ExternalizedSqlViaStringTemplate3
+@RegisterMapper(AccountEmailMapper.class)
+public interface AccountEmailSqlDao extends UpdatableEntityDao<AccountEmail>, Transactional<AccountEmailSqlDao>, Transmogrifier {
+    @Override
+    @SqlUpdate
+    public void create(@AccountEmailBinder final AccountEmail accountEmail,
+                       @CallContextBinder final CallContext context);
+
+    @SqlBatch(transactional = false)
+    public void create(@AccountEmailBinder final List<AccountEmail> accountEmailList,
+                       @CallContextBinder final CallContext context);
+
+    @Override
+    @SqlUpdate
+    public void update(@AccountEmailBinder final AccountEmail accountEmail,
+                       @CallContextBinder final CallContext context);
+
+    @SqlBatch(transactional = false)
+    public void update(@AccountEmailBinder final List<AccountEmail> accountEmailList,
+                       @CallContextBinder final CallContext context);
+
+    @SqlUpdate
+    public void delete(@AccountEmailBinder final AccountEmail accountEmail,
+                       @CallContextBinder final CallContext context);
+
+    @SqlBatch(transactional = false)
+    public void delete(@AccountEmailBinder final List<AccountEmail> accountEmailList,
+                       @CallContextBinder final CallContext context);
+
+    @SqlBatch(transactional=false)
+    public void insertAccountEmailHistoryFromTransaction(@Bind("historyRecordId") final List<String> historyRecordIdList,
+                                                         @AccountEmailBinder final List<AccountEmail> accountEmail,
+                                                         @ChangeTypeBinder final ChangeType changeType,
+                                                         @CallContextBinder final CallContext context);
+
+    @SqlQuery
+    public List<AccountEmail> getByAccountId(@Bind("accountId") final String accountId);
+}
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountMapper.java b/account/src/main/java/com/ning/billing/account/dao/AccountMapper.java
new file mode 100644
index 0000000..5eca437
--- /dev/null
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountMapper.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.account.dao;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.user.AccountBuilder;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.dao.MapperBase;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+public class AccountMapper extends MapperBase implements ResultSetMapper<Account> {
+    @Override
+    public Account map(int index, ResultSet result, StatementContext context) throws SQLException {
+        UUID id = UUID.fromString(result.getString("id"));
+        String externalKey = result.getString("external_key");
+        String email = result.getString("email");
+        String name = result.getString("name");
+        int firstNameLength = result.getInt("first_name_length");
+        int billingCycleDay = result.getInt("billing_cycle_day");
+
+        String currencyString = result.getString("currency");
+        Currency currency = (currencyString == null) ? null : Currency.valueOf(currencyString);
+
+        String paymentProviderName = result.getString("payment_provider_name");
+
+        String timeZoneId = result.getString("time_zone");
+        DateTimeZone timeZone = (timeZoneId == null) ? null : DateTimeZone.forID(timeZoneId);
+
+        String locale = result.getString("locale");
+
+        String address1 = result.getString("address1");
+        String address2 = result.getString("address2");
+        String companyName = result.getString("company_name");
+        String city = result.getString("city");
+        String stateOrProvince = result.getString("state_or_province");
+        String postalCode = result.getString("postal_code");
+        String country = result.getString("country");
+        String phone = result.getString("phone");
+
+        String createdBy = result.getString("created_by");
+        DateTime createdDate = getDate(result, "created_date");
+        String updatedBy = result.getString("updated_by");
+        DateTime updatedDate = getDate(result, "updated_date");
+
+        return new AccountBuilder(id).externalKey(externalKey).email(email)
+                                     .name(name).firstNameLength(firstNameLength)
+                                     .phone(phone).currency(currency)
+                                     .billingCycleDay(billingCycleDay)
+                                     .paymentProviderName(paymentProviderName)
+                                     .timeZone(timeZone).locale(locale)
+                                     .address1(address1).address2(address2)
+                                     .companyName(companyName)
+                                     .city(city).stateOrProvince(stateOrProvince)
+                                     .postalCode(postalCode).country(country)
+                                     .createdBy(createdBy).createdDate(createdDate)
+                                     .updatedBy(updatedBy).updatedDate(updatedDate)
+                                     .build();
+    }
+}
\ No newline at end of file
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountSqlDao.java b/account/src/main/java/com/ning/billing/account/dao/AccountSqlDao.java
index 8c09af4..552ae6d 100644
--- a/account/src/main/java/com/ning/billing/account/dao/AccountSqlDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountSqlDao.java
@@ -16,17 +16,11 @@
 
 package com.ning.billing.account.dao;
 
-import java.sql.ResultSet;
-import java.sql.SQLException;
 import java.util.UUID;
 
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.callcontext.CallContextBinder;
-import com.ning.billing.util.dao.MapperBase;
 import com.ning.billing.util.entity.UpdatableEntityDao;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
-import org.skife.jdbi.v2.StatementContext;
 import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
@@ -34,15 +28,12 @@ import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
 import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
-import org.skife.jdbi.v2.tweak.ResultSetMapper;
 
 import com.ning.billing.account.api.Account;
-import com.ning.billing.account.api.user.AccountBuilder;
-import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.util.UuidMapper;
 
 @ExternalizedSqlViaStringTemplate3
-@RegisterMapper({UuidMapper.class, AccountSqlDao.AccountMapper.class})
+@RegisterMapper({UuidMapper.class, AccountMapper.class})
 public interface AccountSqlDao extends UpdatableEntityDao<Account>, Transactional<AccountSqlDao>, Transmogrifier {
     @SqlQuery
     public Account getAccountByKey(@Bind("externalKey") final String key);
@@ -57,54 +48,4 @@ public interface AccountSqlDao extends UpdatableEntityDao<Account>, Transactiona
     @Override
     @SqlUpdate
     public void update(@AccountBinder Account account, @CallContextBinder final CallContext context);
-
-    public static class AccountMapper extends MapperBase implements ResultSetMapper<Account> {
-        @Override
-        public Account map(int index, ResultSet result, StatementContext context) throws SQLException {
-            UUID id = UUID.fromString(result.getString("id"));
-            String externalKey = result.getString("external_key");
-            String email = result.getString("email");
-            String name = result.getString("name");
-            int firstNameLength = result.getInt("first_name_length");
-            int billingCycleDay = result.getInt("billing_cycle_day");
-
-            String currencyString = result.getString("currency");
-            Currency currency = (currencyString == null) ? null : Currency.valueOf(currencyString);
-
-            String paymentProviderName = result.getString("payment_provider_name");
-
-            String timeZoneId = result.getString("time_zone");
-            DateTimeZone timeZone = (timeZoneId == null) ? null : DateTimeZone.forID(timeZoneId);
-
-            String locale = result.getString("locale");
-
-            String address1 = result.getString("address1");
-            String address2 = result.getString("address2");
-            String companyName = result.getString("company_name");
-            String city = result.getString("city");
-            String stateOrProvince = result.getString("state_or_province");
-            String postalCode = result.getString("postal_code");
-            String country = result.getString("country");
-            String phone = result.getString("phone");
-
-            String createdBy = result.getString("created_by");
-            DateTime createdDate = getDate(result, "created_date");
-            String updatedBy = result.getString("updated_by");
-            DateTime updatedDate = getDate(result, "updated_date");
-
-            return new AccountBuilder(id).externalKey(externalKey).email(email)
-                                         .name(name).firstNameLength(firstNameLength)
-                                         .phone(phone).currency(currency)
-                                         .billingCycleDay(billingCycleDay)
-                                         .paymentProviderName(paymentProviderName)
-                                         .timeZone(timeZone).locale(locale)
-                                         .address1(address1).address2(address2)
-                                         .companyName(companyName)
-                                         .city(city).stateOrProvince(stateOrProvince)
-                                         .postalCode(postalCode).country(country)
-                                         .createdBy(createdBy).createdDate(createdDate)
-                                         .updatedBy(updatedBy).updatedDate(updatedDate)
-                                         .build();
-        }
-    }
 }
diff --git a/account/src/main/java/com/ning/billing/account/dao/AuditedAccountDao.java b/account/src/main/java/com/ning/billing/account/dao/AuditedAccountDao.java
index b9a2b31..1968bc1 100644
--- a/account/src/main/java/com/ning/billing/account/dao/AuditedAccountDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/AuditedAccountDao.java
@@ -17,13 +17,17 @@
 package com.ning.billing.account.dao;
 
 import java.sql.DataTruncation;
+import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 import java.util.UUID;
 
+import com.ning.billing.account.api.AccountEmail;
 import com.ning.billing.util.ChangeType;
 import com.ning.billing.util.audit.dao.AuditSqlDao;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.customfield.dao.CustomFieldDao;
+import com.ning.billing.util.dao.AuditedDaoBase;
 import com.ning.billing.util.entity.EntityPersistenceException;
 import com.ning.billing.util.tag.dao.TagDao;
 import org.skife.jdbi.v2.IDBI;
@@ -42,8 +46,10 @@ import com.ning.billing.util.customfield.dao.CustomFieldSqlDao;
 import com.ning.billing.util.bus.Bus;
 import com.ning.billing.util.tag.Tag;
 
-public class AuditedAccountDao implements AccountDao {
+public class AuditedAccountDao extends AuditedDaoBase implements AccountDao {
+    private static final String ACCOUNT_EMAIL_HISTORY_TABLE = "account_email_history";
     private final AccountSqlDao accountSqlDao;
+    private final AccountEmailSqlDao accountEmailSqlDao;
     private final TagDao tagDao;
     private final CustomFieldDao customFieldDao;
     private final Bus eventBus;
@@ -52,6 +58,7 @@ public class AuditedAccountDao implements AccountDao {
     public AuditedAccountDao(IDBI dbi, Bus eventBus, TagDao tagDao, CustomFieldDao customFieldDao) {
         this.eventBus = eventBus;
         this.accountSqlDao = dbi.onDemand(AccountSqlDao.class);
+        this.accountEmailSqlDao = dbi.onDemand(AccountEmailSqlDao.class);
         this.tagDao = tagDao;
         this.customFieldDao = customFieldDao;
     }
@@ -197,6 +204,64 @@ public class AuditedAccountDao implements AccountDao {
     }
 
     @Override
+    public List<AccountEmail> getEmails(final UUID accountId) {
+        return accountEmailSqlDao.getByAccountId(accountId.toString());
+    }
+
+    @Override
+    public void saveEmails(final UUID accountId, final List<AccountEmail> emails, final CallContext context) {
+        final List<AccountEmail> existingEmails = accountEmailSqlDao.getByAccountId(accountId.toString());
+        final List<AccountEmail> updatedEmails = new ArrayList<AccountEmail>();
+
+        Iterator<AccountEmail> existingEmailIterator = existingEmails.iterator();
+        while (existingEmailIterator.hasNext()) {
+            AccountEmail existingEmail = existingEmailIterator.next();
+
+            Iterator<AccountEmail> newEmailIterator = emails.iterator();
+            while (newEmailIterator.hasNext()) {
+                AccountEmail newEmail = newEmailIterator.next();
+                if (newEmail.getId().equals(existingEmail.getId())) {
+                    // check equality; if not equal, add to updated
+                    if (!newEmail.equals(existingEmail)) {
+                        updatedEmails.add(newEmail);
+                    }
+
+                    // remove from both
+                    newEmailIterator.remove();
+                    existingEmailIterator.remove();
+                }
+            }
+        }
+
+        // remaining emails in newEmail are inserts; remaining emails in existingEmail are deletes
+        accountEmailSqlDao.inTransaction(new Transaction<Void, AccountEmailSqlDao>() {
+            @Override
+            public Void inTransaction(AccountEmailSqlDao dao, TransactionStatus transactionStatus) throws Exception {
+                dao.create(emails, context);
+                dao.update(updatedEmails, context);
+                dao.delete(existingEmails, context);
+
+                List<String> insertHistoryIdList = getIdList(emails.size());
+                List<String> updateHistoryIdList = getIdList(updatedEmails.size());
+                List<String> deleteHistoryIdList = getIdList(existingEmails.size());
+
+                // insert histories
+                dao.insertAccountEmailHistoryFromTransaction(insertHistoryIdList, emails, ChangeType.INSERT, context);
+                dao.insertAccountEmailHistoryFromTransaction(updateHistoryIdList, updatedEmails, ChangeType.UPDATE, context);
+                dao.insertAccountEmailHistoryFromTransaction(deleteHistoryIdList, existingEmails, ChangeType.DELETE, context);
+
+                // insert audits
+                AuditSqlDao auditSqlDao = dao.become(AuditSqlDao.class);
+                auditSqlDao.insertAuditFromTransaction(ACCOUNT_EMAIL_HISTORY_TABLE, insertHistoryIdList, ChangeType.INSERT, context);
+                auditSqlDao.insertAuditFromTransaction(ACCOUNT_EMAIL_HISTORY_TABLE, updateHistoryIdList, ChangeType.UPDATE, context);
+                auditSqlDao.insertAuditFromTransaction(ACCOUNT_EMAIL_HISTORY_TABLE, deleteHistoryIdList, ChangeType.DELETE, context);
+
+                return null;
+            }
+        });
+    }
+
+    @Override
     public void test() {
         accountSqlDao.test();
     }
@@ -229,4 +294,6 @@ public class AuditedAccountDao implements AccountDao {
                                                        final CallContext context) {
         customFieldDao.saveFields(transactionalDao, account.getId(), account.getObjectName(), account.getFieldList(), context);
     }
+
+
 }
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
new file mode 100644
index 0000000..d287c08
--- /dev/null
+++ b/account/src/main/resources/com/ning/billing/account/dao/AccountEmailSqlDao.sql.stg
@@ -0,0 +1,48 @@
+group account_emails;
+
+fields(prefix) ::= <<
+    <prefix>id,
+    <prefix>account_id,
+    <prefix>email,
+    <prefix>created_by,
+    <prefix>created_date,
+    <prefix>updated_by,
+    <prefix>updated_date
+>>
+
+create() ::= <<
+    INSERT INTO account_emails(<fields()>)
+    VALUES
+    (:id, :accountId, :email, :userName, :createdDate, :userName, :updatedDate);
+>>
+
+update() ::= <<
+    UPDATE account_emails
+    SET email = :email, updated_by = :userName, updated_date = :updatedDate;
+>>
+
+delete() ::= <<
+    DELETE FROM account_emails
+    WHERE id = :id;
+>>
+
+insertAccountEmailHistoryFromTransaction() ::= <<
+    INSERT INTO account_email_history(history_record_id, id, account_id, email, change_type, updated_by, date)
+    VALUES (:historyRecordId, :id, :accountId, :email, :changeType, :userName, :updatedDate);
+>>
+
+getById() ::= <<
+    SELECT <fields()> FROM account_emails WHERE id = :id;
+>>
+
+get() ::= <<
+    SELECT <fields()> FROM account_emails;
+>>
+
+getByAccountId() ::= <<
+    SELECT <fields()> FROM account_emails WHERE account_id = :accountId;
+>>
+
+test() ::= <<
+    SELECT 1 FROM account_emails;
+>>
\ No newline at end of file
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 ff9a41c..696133a 100644
--- a/account/src/main/resources/com/ning/billing/account/ddl.sql
+++ b/account/src/main/resources/com/ning/billing/account/ddl.sql
@@ -51,6 +51,31 @@ CREATE TABLE account_history (
     phone varchar(25) DEFAULT NULL,
     change_type char(6) NOT NULL,
     updated_by varchar(50) NOT NULL,
-    date datetime
+    date datetime NOT NULL
 ) ENGINE=innodb;
-CREATE INDEX account_id ON account_history(id);
\ No newline at end of file
+CREATE INDEX account_id ON account_history(id);
+
+DROP TABLE IF EXISTS account_emails;
+CREATE TABLE account_emails (
+    id char(36) NOT NULL,
+    account_id char(36) NOT NULL,
+    email varchar(50) NOT NULL,
+    created_by varchar(50) NOT NULL,
+    created_date datetime NOT NULL,
+    updated_by varchar(50) NOT NULL,
+    updated_date datetime NOT NULL,
+    PRIMARY KEY(id)
+) ENGINE=innodb;
+CREATE INDEX account_email_account_id ON account_mails(account_id);
+
+DROP TABLE IF EXISTS account_email_history;
+CREATE TABLE account_email_history (
+    history_record_id char(36) NOT NULL,
+    id char(36) NOT NULL,
+    account_id char(36) NOT NULL,
+    email varchar(50) NOT NULL,
+    change_type char(6) NOT NULL,
+    updated_by varchar(50) NOT NULL,
+    date datetime NOT NULL,
+    PRIMARY KEY(history_record_id)
+) ENGINE=innodb;
\ No newline at end of file
diff --git a/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java b/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java
index d2fea9e..27f72f3 100644
--- a/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java
+++ b/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java
@@ -102,6 +102,16 @@ public class MockAccountUserApi implements AccountUserApi {
     }
 
     @Override
+    public List<AccountEmail> getEmails(UUID accountId) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void saveEmails(UUID accountId, List<AccountEmail> emails, CallContext context) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public void updateAccount(final Account account, final CallContext context) {
         throw new UnsupportedOperationException();
     }
diff --git a/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java b/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java
index 8761c81..d86a15c 100644
--- a/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java
+++ b/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java
@@ -26,12 +26,15 @@ import com.google.inject.Inject;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.account.api.AccountChangeEvent;
+import com.ning.billing.account.api.AccountEmail;
 import com.ning.billing.account.api.user.DefaultAccountChangeNotification;
 import com.ning.billing.account.api.user.DefaultAccountCreationEvent;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.bus.Bus;
 import com.ning.billing.util.bus.Bus.EventBusException;
 
+import javax.annotation.Nullable;
+
 public class MockAccountDao implements AccountDao {
     private final Bus eventBus;
     private final Map<String, Account> accounts = new ConcurrentHashMap<String, Account>();
@@ -84,6 +87,16 @@ public class MockAccountDao implements AccountDao {
     }
 
     @Override
+    public List<AccountEmail> getEmails(UUID accountId) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void saveEmails(UUID accountId, List<AccountEmail> emails, CallContext context) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public void update(Account account, CallContext context) {
         Account currentAccount = accounts.put(account.getId().toString(), account);
 
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java b/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
index be061f9..15ebf67 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
@@ -54,7 +54,7 @@ public class TestAnalyticsListener
     @BeforeMethod(alwaysRun = true)
     public void setUp() throws Exception
     {
-        final BusinessSubscriptionTransitionRecorder recorder = new BusinessSubscriptionTransitionRecorder(dao, new MockEntitlementUserApi(bundleUUID, KEY), new MockIAccountUserApi(ACCOUNT_KEY, CURRENCY));
+        final BusinessSubscriptionTransitionRecorder recorder = new BusinessSubscriptionTransitionRecorder(dao, new MockEntitlementUserApi(bundleUUID, KEY), new MockAccountUserApi(ACCOUNT_KEY, CURRENCY));
         listener = new AnalyticsListener(recorder, null);
     }
 
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountEmail.java b/api/src/main/java/com/ning/billing/account/api/AccountEmail.java
new file mode 100644
index 0000000..88ec469
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/account/api/AccountEmail.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.account.api;
+
+import com.ning.billing.util.entity.UpdatableEntity;
+
+import java.util.UUID;
+
+public interface AccountEmail extends UpdatableEntity {
+    UUID getAccountId();
+    String getEmail();
+}
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 98d6272..5ea43d0 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
@@ -51,4 +51,8 @@ public interface AccountUserApi {
     public List<Account> getAccounts();
 
     public UUID getIdFromKey(String externalKey) throws AccountApiException;
+
+    public List<AccountEmail> getEmails(UUID accountId);
+
+    public void saveEmails(UUID accountId, List<AccountEmail> emails, CallContext context);
 }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java
index 3650bf9..d6648a1 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java
@@ -82,7 +82,7 @@ public class TestDefaultEntitlementBillingApi {
     private CallContextFactory factory;
 	private DateTime subscriptionStartDate;
 
-	@BeforeSuite(alwaysRun=true)
+	@BeforeSuite(groups={"fast", "slow"})
 	public void setup() throws ServiceException {
 		TestApiBase.loadSystemPropertiesFromClasspath("/entitlement.properties");
         final Injector g = Guice.createInjector(Stage.PRODUCTION, new CatalogModule(), new ClockModule(), new CallContextModule());
@@ -94,7 +94,7 @@ public class TestDefaultEntitlementBillingApi {
         ((DefaultCatalogService)catalogService).loadCatalog();
 	}
 
-	@BeforeMethod(alwaysRun=true)
+	@BeforeMethod(groups={"fast", "slow"})
 	public void setupEveryTime() {
 		bundles = new ArrayList<SubscriptionBundle>();
 		final SubscriptionBundle bundle = new SubscriptionBundleData( zeroId,"TestKey", oneId,  clock.getUTCNow().minusDays(4));
diff --git a/util/src/main/java/com/ning/billing/util/dao/AuditedDaoBase.java b/util/src/main/java/com/ning/billing/util/dao/AuditedDaoBase.java
new file mode 100644
index 0000000..03417b4
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/dao/AuditedDaoBase.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.dao;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+public abstract class AuditedDaoBase {
+    protected List<String> getIdList(int size) {
+        List<String> results = new ArrayList<String>();
+        for (int i = 0; i < size; i++) {
+            results.add(UUID.randomUUID().toString());
+        }
+        return results;
+    }
+}
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 374e0a3..9798250 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
@@ -22,18 +22,18 @@ import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.util.ChangeType;
 import com.ning.billing.util.audit.dao.AuditSqlDao;
 import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.dao.AuditedDaoBase;
 import com.ning.billing.util.tag.Tag;
 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 java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import java.util.UUID;
 
-public class AuditedTagDao implements TagDao {
+public class AuditedTagDao extends AuditedDaoBase implements TagDao {
     private final TagSqlDao tagSqlDao;
 
     @Inject
@@ -85,14 +85,6 @@ public class AuditedTagDao implements TagDao {
         auditSqlDao.insertAuditFromTransaction("tag_history", historyIdsForDelete, ChangeType.DELETE, context);
     }
 
-    private List<String> getIdList(int size) {
-        List<String> results = new ArrayList<String>();
-        for (int i = 0; i < size; i++) {
-            results.add(UUID.randomUUID().toString());
-        }
-        return results;
-    }
-
     @Override
     public List<Tag> loadTags(final UUID objectId, final String objectType) {
         return tagSqlDao.load(objectId.toString(), objectType);
diff --git a/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java b/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java
index a8d353f..99226e2 100644
--- a/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java
+++ b/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java
@@ -391,6 +391,8 @@ public class TestTagStore {
         String query = String.format("select * from audit_log a inner join tag_history th on a.record_id = th.history_record_id where a.table_name = 'tag_history' and th.id='%s' and a.change_type='INSERT'",
                                      tag.getId().toString());
         List<Map<String, Object>> result = handle.select(query);
+        handle.close();
+
         assertNotNull(result);
         assertEquals(result.size(), 1);
         assertEquals(result.get(0).get("change_type"), "INSERT");
@@ -420,6 +422,8 @@ public class TestTagStore {
         String query = String.format("select * from audit_log a inner join tag_history th on a.record_id = th.history_record_id where a.table_name = 'tag_history' and th.id='%s' and a.change_type='DELETE'",
                                      tag.getId().toString());
         List<Map<String, Object>> result = handle.select(query);
+        handle.close();
+
         assertNotNull(result);
         assertEquals(result.size(), 1);
         assertNotNull(result.get(0).get("change_date"));