Details
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 d7d8049..057aaf6 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
@@ -145,4 +145,14 @@ public class DefaultAccountUserApi implements AccountUserApi {
public void saveEmails(final UUID accountId, final List<AccountEmail> newEmails, final CallContext context) {
accountEmailDao.saveEmails(accountId, newEmails, context);
}
+
+ @Override
+ public void addEmail(final UUID accountId, final AccountEmail email, final CallContext context) {
+ accountEmailDao.addEmail(accountId, email, context);
+ }
+
+ @Override
+ public void removeEmail(final UUID accountId, final AccountEmail email, final CallContext context) {
+ accountEmailDao.removeEmail(accountId, email, 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 2c470d3..09f2cda 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
@@ -34,5 +34,9 @@ public interface AccountEmailDao {
*/
public void saveEmails(UUID accountId, List<AccountEmail> emails, CallContext context);
+ public void addEmail(UUID accountId, AccountEmail email, CallContext context);
+
+ public void removeEmail(UUID accountId, AccountEmail email, CallContext context);
+
public void test();
}
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountEmailHistoryBinder.java b/account/src/main/java/com/ning/billing/account/dao/AccountEmailHistoryBinder.java
index 01344b3..ed9ebae 100644
--- a/account/src/main/java/com/ning/billing/account/dao/AccountEmailHistoryBinder.java
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountEmailHistoryBinder.java
@@ -16,33 +16,34 @@
package com.ning.billing.account.dao;
-import com.ning.billing.account.api.AccountEmail;
-import com.ning.billing.util.dao.EntityHistory;
-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;
+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 com.ning.billing.account.api.AccountEmail;
+import com.ning.billing.util.dao.EntityHistory;
+
@BindingAnnotation(AccountEmailHistoryBinder.AccountEmailHistoryBinderFactory.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
public @interface AccountEmailHistoryBinder {
public static class AccountEmailHistoryBinderFactory implements BinderFactory {
@Override
- public Binder<AccountEmailHistoryBinder, EntityHistory<AccountEmail>> build(Annotation annotation) {
+ public Binder<AccountEmailHistoryBinder, EntityHistory<AccountEmail>> build(final Annotation annotation) {
return new Binder<AccountEmailHistoryBinder, EntityHistory<AccountEmail>>() {
@Override
- public void bind(SQLStatement q, AccountEmailHistoryBinder bind, EntityHistory<AccountEmail> history) {
+ public void bind(SQLStatement q, final AccountEmailHistoryBinder bind, final EntityHistory<AccountEmail> history) {
q.bind("recordId", history.getValue());
q.bind("changeType", history.getChangeType().toString());
- AccountEmail accountEmail = history.getEntity();
+ final AccountEmail accountEmail = history.getEntity();
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/AuditedAccountEmailDao.java b/account/src/main/java/com/ning/billing/account/dao/AuditedAccountEmailDao.java
index d8fb806..f5f50ed 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
@@ -17,12 +17,17 @@
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.google.common.collect.ImmutableList;
import com.google.inject.Inject;
import com.ning.billing.account.api.AccountEmail;
import com.ning.billing.util.callcontext.CallContext;
@@ -55,6 +60,50 @@ public class AuditedAccountEmailDao extends AuditedCollectionDaoBase<AccountEmai
}
@Override
+ public void addEmail(final UUID accountId, final AccountEmail email, final CallContext context) {
+ accountEmailSqlDao.inTransaction(new Transaction<Object, AccountEmailSqlDao>() {
+ @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);
+ final Map<String, AccountEmail> newEmails = new HashMap<String, AccountEmail>();
+ for (final AccountEmail currentEmail : currentEmails) {
+ newEmails.put(currentEmail.getEmail(), currentEmail);
+ }
+ newEmails.put(email.getEmail(), email);
+
+ saveEntitiesFromTransaction(getSqlDao(), accountId, ObjectType.ACCOUNT_EMAIL,
+ ImmutableList.<AccountEmail>copyOf(newEmails.values()), context);
+
+ return null;
+ }
+ });
+ }
+
+ @Override
+ public void removeEmail(final UUID accountId, final AccountEmail email, final CallContext context) {
+ accountEmailSqlDao.inTransaction(new Transaction<Object, AccountEmailSqlDao>() {
+ @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);
+ 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(), accountId, ObjectType.ACCOUNT_EMAIL,
+ ImmutableList.<AccountEmail>copyOf(newEmails.values()), context);
+
+ return null;
+ }
+ });
+ }
+
+ @Override
public String getKey(final AccountEmail entity) {
return entity.getEmail();
}
@@ -70,7 +119,7 @@ public class AuditedAccountEmailDao extends AuditedCollectionDaoBase<AccountEmai
}
@Override
- protected UpdatableEntityCollectionSqlDao<AccountEmail> transmogrifyDao(Transmogrifier transactionalDao) {
+ protected UpdatableEntityCollectionSqlDao<AccountEmail> transmogrifyDao(final Transmogrifier transactionalDao) {
return transactionalDao.become(AccountEmailSqlDao.class);
}
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 7aeac80..21a78fe 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
@@ -109,6 +109,16 @@ public class MockAccountUserApi implements AccountUserApi {
}
@Override
+ public void addEmail(final UUID accountId, final AccountEmail email, final CallContext context) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void removeEmail(final UUID accountId, final AccountEmail email, final 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/api/user/TestDefaultAccountUserApi.java b/account/src/test/java/com/ning/billing/account/api/user/TestDefaultAccountUserApi.java
index e3e2f84..27134fa 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
@@ -27,9 +27,11 @@ import org.testng.annotations.Test;
import com.ning.billing.account.api.Account;
import com.ning.billing.account.api.AccountData;
import com.ning.billing.account.api.DefaultAccount;
+import com.ning.billing.account.api.DefaultAccountEmail;
import com.ning.billing.account.dao.AccountDao;
import com.ning.billing.account.dao.AccountEmailDao;
import com.ning.billing.account.dao.MockAccountDao;
+import com.ning.billing.account.dao.MockAccountEmailDao;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.util.bus.Bus;
import com.ning.billing.util.callcontext.CallContext;
@@ -38,14 +40,15 @@ import com.ning.billing.util.callcontext.CallContextFactory;
public class TestDefaultAccountUserApi {
private final CallContextFactory factory = Mockito.mock(CallContextFactory.class);
private final CallContext callContext = Mockito.mock(CallContext.class);
- private final AccountEmailDao accountEmailDao = Mockito.mock(AccountEmailDao.class);
private AccountDao accountDao;
+ private AccountEmailDao accountEmailDao;
private DefaultAccountUserApi accountUserApi;
@BeforeMethod(groups = "fast")
public void setUp() throws Exception {
accountDao = new MockAccountDao(Mockito.mock(Bus.class));
+ accountEmailDao = new MockAccountEmailDao();
accountUserApi = new DefaultAccountUserApi(factory, accountDao, accountEmailDao);
}
@@ -98,4 +101,26 @@ public class TestDefaultAccountUserApi {
Assert.assertEquals(account.isMigrated(), isMigrated);
Assert.assertEquals(account.isNotifiedForInvoices(), isNotifiedForInvoices);
}
+
+ @Test(groups = "fast")
+ public void testAddEmail() throws Exception {
+ final UUID accountId = UUID.randomUUID();
+
+ // Verify the initial state
+ Assert.assertEquals(accountEmailDao.getEmails(accountId).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).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).size(), 2);
+
+ // Remove the first second one
+ accountUserApi.removeEmail(accountId, new DefaultAccountEmail(accountId, email1), callContext);
+ Assert.assertEquals(accountEmailDao.getEmails(accountId).size(), 1);
+ }
}
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
new file mode 100644
index 0000000..933ca54
--- /dev/null
+++ b/account/src/test/java/com/ning/billing/account/dao/MockAccountEmailDao.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2010-2012 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 java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.google.common.collect.ImmutableList;
+import com.ning.billing.account.api.AccountEmail;
+import com.ning.billing.util.callcontext.CallContext;
+
+public class MockAccountEmailDao implements AccountEmailDao {
+ private final Map<UUID, Set<AccountEmail>> emails = new ConcurrentHashMap<UUID, Set<AccountEmail>>();
+
+ @Override
+ public List<AccountEmail> getEmails(final UUID accountId) {
+ final Set<AccountEmail> accountEmails = emails.get(accountId);
+ if (accountEmails == null) {
+ return ImmutableList.<AccountEmail>of();
+ } else {
+ return ImmutableList.<AccountEmail>copyOf(accountEmails.iterator());
+ }
+ }
+
+ @Override
+ public void saveEmails(final UUID accountId, final List<AccountEmail> newEmails, final CallContext 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 CallContext 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 CallContext context) {
+ if (emails.get(accountId) == null) {
+ emails.put(accountId, new HashSet<AccountEmail>());
+ }
+
+ emails.get(accountId).remove(email);
+ }
+
+ @Override
+ public void test() {
+ }
+}
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 5e51ecd..4c9205c 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
@@ -16,27 +16,11 @@
package com.ning.billing.account.dao;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
-
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
-import com.ning.billing.account.api.AccountEmail;
-import com.ning.billing.account.api.DefaultAccountEmail;
-import com.ning.billing.util.api.TagApiException;
-import com.ning.billing.util.customfield.CustomField;
-import com.ning.billing.util.customfield.StringCustomField;
-import com.ning.billing.util.customfield.dao.AuditedCustomFieldDao;
-import com.ning.billing.util.customfield.dao.CustomFieldDao;
-import com.ning.billing.util.dao.ObjectType;
-import com.ning.billing.util.entity.EntityPersistenceException;
-import com.ning.billing.util.tag.dao.AuditedTagDao;
-import com.ning.billing.util.tag.dao.TagDao;
import org.joda.time.DateTimeZone;
import org.skife.jdbi.v2.Handle;
import org.testng.annotations.Test;
@@ -44,40 +28,56 @@ import org.testng.annotations.Test;
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.DefaultAccountEmail;
import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.api.TagApiException;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.customfield.StringCustomField;
+import com.ning.billing.util.customfield.dao.AuditedCustomFieldDao;
+import com.ning.billing.util.customfield.dao.CustomFieldDao;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.entity.EntityPersistenceException;
import com.ning.billing.util.tag.DefaultTagDefinition;
import com.ning.billing.util.tag.Tag;
import com.ning.billing.util.tag.TagDefinition;
+import com.ning.billing.util.tag.dao.AuditedTagDao;
+import com.ning.billing.util.tag.dao.TagDao;
import com.ning.billing.util.tag.dao.TagDefinitionSqlDao;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
@Test(groups = {"slow", "account-dao"})
public class TestAccountDao extends AccountDaoTestBase {
- private Account createTestAccount(int billCycleDay) {
+ private Account createTestAccount(final int billCycleDay) {
return createTestAccount(billCycleDay, "123-456-7890");
}
- private Account createTestAccount(int billCycleDay, String phone) {
- String thisKey = "test" + UUID.randomUUID().toString();
- String lastName = UUID.randomUUID().toString();
- String thisEmail = "me@me.com" + " " + UUID.randomUUID();
- String firstName = "Bob";
- String name = firstName + " " + lastName;
- String locale = "EN-US";
- DateTimeZone timeZone = DateTimeZone.forID("America/Los_Angeles");
- int firstNameLength = firstName.length();
+ private Account createTestAccount(final int billCycleDay, final String phone) {
+ final String thisKey = "test" + UUID.randomUUID().toString();
+ final String lastName = UUID.randomUUID().toString();
+ final String thisEmail = "me@me.com" + " " + UUID.randomUUID();
+ final String firstName = "Bob";
+ final String name = firstName + " " + lastName;
+ final String locale = "EN-US";
+ final DateTimeZone timeZone = DateTimeZone.forID("America/Los_Angeles");
+ final int firstNameLength = firstName.length();
return new DefaultAccount(UUID.randomUUID(), thisKey, thisEmail, name, firstNameLength, Currency.USD,
- billCycleDay, UUID.randomUUID(), timeZone, locale,
- null, null, null, null, null, null, null, // add null address fields
- phone, false, false);
+ billCycleDay, UUID.randomUUID(), timeZone, locale,
+ null, null, null, null, null, null, null, // add null address fields
+ phone, false, false);
}
@Test
public void testBasic() throws EntityPersistenceException {
- Account a = createTestAccount(5);
+ final Account a = createTestAccount(5);
accountDao.create(a, context);
- String key = a.getExternalKey();
+ final String key = a.getExternalKey();
Account r = accountDao.getAccountByKey(key);
assertNotNull(r);
@@ -87,7 +87,7 @@ public class TestAccountDao extends AccountDaoTestBase {
assertNotNull(r);
assertEquals(r.getExternalKey(), a.getExternalKey());
- List<Account> all = accountDao.get();
+ final List<Account> all = accountDao.get();
assertNotNull(all);
assertTrue(all.size() >= 1);
}
@@ -95,27 +95,27 @@ public class TestAccountDao extends AccountDaoTestBase {
// simple test to ensure long phone numbers can be stored
@Test
public void testLongPhoneNumber() throws EntityPersistenceException {
- Account account = createTestAccount(1, "123456789012345678901234");
+ final Account account = createTestAccount(1, "123456789012345678901234");
accountDao.create(account, context);
- Account saved = accountDao.getAccountByKey(account.getExternalKey());
+ final Account saved = accountDao.getAccountByKey(account.getExternalKey());
assertNotNull(saved);
}
// simple test to ensure excessively long phone numbers cannot be stored
@Test(expectedExceptions = {EntityPersistenceException.class})
public void testOverlyLongPhoneNumber() throws EntityPersistenceException {
- Account account = createTestAccount(1, "12345678901234567890123456");
+ final Account account = createTestAccount(1, "12345678901234567890123456");
accountDao.create(account, context);
}
@Test
public void testGetById() throws EntityPersistenceException {
Account account = createTestAccount(1);
- UUID id = account.getId();
- String key = account.getExternalKey();
- String name = account.getName();
- int firstNameLength = account.getFirstNameLength();
+ final UUID id = account.getId();
+ final String key = account.getExternalKey();
+ final String name = account.getName();
+ final int firstNameLength = account.getFirstNameLength();
accountDao.create(account, context);
@@ -130,45 +130,45 @@ public class TestAccountDao extends AccountDaoTestBase {
@Test
public void testCustomFields() throws EntityPersistenceException {
- String fieldName = "testField1";
- String fieldValue = "testField1_value";
+ final String fieldName = "testField1";
+ final String fieldValue = "testField1_value";
- UUID accountId = UUID.randomUUID();
- List<CustomField> customFields = new ArrayList<CustomField>();
+ final UUID accountId = UUID.randomUUID();
+ final List<CustomField> customFields = new ArrayList<CustomField>();
customFields.add(new StringCustomField(fieldName, fieldValue));
- CustomFieldDao customFieldDao = new AuditedCustomFieldDao(dbi);
+ final CustomFieldDao customFieldDao = new AuditedCustomFieldDao(dbi);
customFieldDao.saveEntities(accountId, ObjectType.ACCOUNT, customFields, context);
- Map<String, CustomField> customFieldMap = customFieldDao.loadEntities(accountId, ObjectType.ACCOUNT);
+ final Map<String, CustomField> customFieldMap = customFieldDao.loadEntities(accountId, ObjectType.ACCOUNT);
assertEquals(customFieldMap.size(), 1);
- CustomField customField = customFieldMap.get(fieldName);
+ final CustomField customField = customFieldMap.get(fieldName);
assertEquals(customField.getName(), fieldName);
assertEquals(customField.getValue(), fieldValue);
}
@Test
public void testTags() throws EntityPersistenceException, TagApiException {
- Account account = createTestAccount(1);
- TagDefinition definition = new DefaultTagDefinition("Test Tag", "For testing only", false);
- TagDefinitionSqlDao tagDescriptionDao = dbi.onDemand(TagDefinitionSqlDao.class);
+ final Account account = createTestAccount(1);
+ final TagDefinition definition = new DefaultTagDefinition("Test Tag", "For testing only", false);
+ final TagDefinitionSqlDao tagDescriptionDao = dbi.onDemand(TagDefinitionSqlDao.class);
tagDescriptionDao.create(definition, context);
- TagDao tagDao = new AuditedTagDao(dbi, tagEventBuilder, bus);
+ final TagDao tagDao = new AuditedTagDao(dbi, tagEventBuilder, bus);
tagDao.insertTag(account.getId(), ObjectType.ACCOUNT, definition, context);
- Map<String, Tag> tagMap = tagDao.loadEntities(account.getId(), ObjectType.ACCOUNT);
+ final Map<String, Tag> tagMap = tagDao.loadEntities(account.getId(), ObjectType.ACCOUNT);
assertEquals(tagMap.size(), 1);
- Tag tag = tagMap.get(definition.getName());
+ final Tag tag = tagMap.get(definition.getName());
assertEquals(tag.getTagDefinitionName(), definition.getName());
}
@Test
public void testGetIdFromKey() throws EntityPersistenceException {
- Account account = createTestAccount(1);
+ final Account account = createTestAccount(1);
accountDao.create(account, context);
try {
- UUID accountId = accountDao.getIdFromKey(account.getExternalKey());
+ final UUID accountId = accountDao.getIdFromKey(account.getExternalKey());
assertEquals(accountId, account.getId());
} catch (AccountApiException a) {
fail("Retrieving account failed.");
@@ -177,7 +177,7 @@ public class TestAccountDao extends AccountDaoTestBase {
@Test(expectedExceptions = AccountApiException.class)
public void testGetIdFromKeyForNullKey() throws AccountApiException {
- String key = null;
+ final String key = null;
accountDao.getIdFromKey(key);
}
@@ -186,7 +186,7 @@ public class TestAccountDao extends AccountDaoTestBase {
final Account account = createTestAccount(1);
accountDao.create(account, context);
- AccountData accountData = new AccountData() {
+ final AccountData accountData = new AccountData() {
@Override
public String getExternalKey() {
return account.getExternalKey();
@@ -236,6 +236,7 @@ public class TestAccountDao extends AccountDaoTestBase {
public UUID getPaymentMethodId() {
return account.getPaymentMethodId();
}
+
@Override
public DateTimeZone getTimeZone() {
return DateTimeZone.forID("Australia/Darwin");
@@ -245,6 +246,7 @@ public class TestAccountDao extends AccountDaoTestBase {
public String getLocale() {
return "FR-CA";
}
+
@Override
public String getAddress1() {
return null;
@@ -281,10 +283,10 @@ public class TestAccountDao extends AccountDaoTestBase {
}
};
- Account updatedAccount = new DefaultAccount(account.getId(), accountData);
+ final Account updatedAccount = new DefaultAccount(account.getId(), accountData);
accountDao.update(updatedAccount, context);
- Account savedAccount = accountDao.getAccountByKey(account.getExternalKey());
+ final Account savedAccount = accountDao.getAccountByKey(account.getExternalKey());
assertNotNull(savedAccount);
assertEquals(savedAccount.getName(), updatedAccount.getName());
@@ -305,31 +307,31 @@ public class TestAccountDao extends AccountDaoTestBase {
@Test
public void testAddingContactInformation() throws Exception {
- UUID accountId = UUID.randomUUID();
- DefaultAccount account = new DefaultAccount(accountId, "extKey123456", "myemail123456@glam.com",
- "John Smith", 4, Currency.USD, 15, null,
- DateTimeZone.forID("America/Cambridge_Bay"), "EN-CA",
- null, null, null, null, null, null, null, null, false, false);
+ final UUID accountId = UUID.randomUUID();
+ final DefaultAccount account = new DefaultAccount(accountId, "extKey123456", "myemail123456@glam.com",
+ "John Smith", 4, Currency.USD, 15, null,
+ DateTimeZone.forID("America/Cambridge_Bay"), "EN-CA",
+ null, null, null, null, null, null, null, null, false, false);
accountDao.create(account, context);
- String address1 = "123 address 1";
- String address2 = "456 address 2";
- String companyName = "Some Company";
- String city = "Cambridge Bay";
- String stateOrProvince = "Nunavut";
- String country = "Canada";
- String postalCode = "X0B 0C0";
- String phone = "18001112222";
-
- DefaultAccount updatedAccount = new DefaultAccount(accountId, "extKey123456", "myemail123456@glam.com",
- "John Smith", 4, Currency.USD, 15, null,
- DateTimeZone.forID("America/Cambridge_Bay"), "EN-CA",
- address1, address2, companyName, city, stateOrProvince, country,
- postalCode, phone, false, false);
+ final String address1 = "123 address 1";
+ final String address2 = "456 address 2";
+ final String companyName = "Some Company";
+ final String city = "Cambridge Bay";
+ final String stateOrProvince = "Nunavut";
+ final String country = "Canada";
+ final String postalCode = "X0B 0C0";
+ final String phone = "18001112222";
+
+ final DefaultAccount updatedAccount = new DefaultAccount(accountId, "extKey123456", "myemail123456@glam.com",
+ "John Smith", 4, Currency.USD, 15, null,
+ DateTimeZone.forID("America/Cambridge_Bay"), "EN-CA",
+ address1, address2, companyName, city, stateOrProvince, country,
+ postalCode, phone, false, false);
accountDao.update(updatedAccount, context);
- Account savedAccount = accountDao.getById(accountId);
+ final Account savedAccount = accountDao.getById(accountId);
assertNotNull(savedAccount);
assertEquals(savedAccount.getId(), accountId);
@@ -345,25 +347,25 @@ public class TestAccountDao extends AccountDaoTestBase {
@Test
public void testRemovingContactInformation() throws Exception {
- UUID accountId = UUID.randomUUID();
-
- DefaultAccount account = new DefaultAccount(accountId, "extKey654321", "myemail654321@glam.com",
- "John Smith", 4, Currency.USD, 15, null,
- DateTimeZone.forID("America/Cambridge_Bay"), "EN-CA",
- "123 address 1", "456 address 2", null, "Cambridge Bay",
- "Nunavut", "Canada", "X0B 0C0", "18001112222",
- false, false);
+ final UUID accountId = UUID.randomUUID();
+
+ final DefaultAccount account = new DefaultAccount(accountId, "extKey654321", "myemail654321@glam.com",
+ "John Smith", 4, Currency.USD, 15, null,
+ DateTimeZone.forID("America/Cambridge_Bay"), "EN-CA",
+ "123 address 1", "456 address 2", null, "Cambridge Bay",
+ "Nunavut", "Canada", "X0B 0C0", "18001112222",
+ false, false);
accountDao.create(account, context);
- DefaultAccount updatedAccount = new DefaultAccount(accountId, "extKey654321", "myemail654321@glam.com",
- "John Smith", 4, Currency.USD, 15, null,
- DateTimeZone.forID("America/Cambridge_Bay"), "EN-CA",
- null, null, null, null, null, null, null, null,
- false, false);
+ final DefaultAccount updatedAccount = new DefaultAccount(accountId, "extKey654321", "myemail654321@glam.com",
+ "John Smith", 4, Currency.USD, 15, null,
+ DateTimeZone.forID("America/Cambridge_Bay"), "EN-CA",
+ null, null, null, null, null, null, null, null,
+ false, false);
accountDao.update(updatedAccount, context);
- Account savedAccount = accountDao.getById(accountId);
+ final Account savedAccount = accountDao.getById(accountId);
assertNotNull(savedAccount);
assertEquals(savedAccount.getId(), accountId);
@@ -379,19 +381,19 @@ public class TestAccountDao extends AccountDaoTestBase {
@Test(expectedExceptions = EntityPersistenceException.class)
public void testExternalKeyCannotBeUpdated() throws Exception {
- UUID accountId = UUID.randomUUID();
- String originalExternalKey = "extKey1337";
+ final UUID accountId = UUID.randomUUID();
+ final String originalExternalKey = "extKey1337";
- DefaultAccount account = new DefaultAccount(accountId, originalExternalKey, "myemail1337@glam.com",
- "John Smith", 4, Currency.USD, 15, null,
- null, null, null, null, null, null, null, null, null, null,
- false, false);
+ final DefaultAccount account = new DefaultAccount(accountId, originalExternalKey, "myemail1337@glam.com",
+ "John Smith", 4, Currency.USD, 15, null,
+ null, null, null, null, null, null, null, null, null, null,
+ false, false);
accountDao.create(account, context);
- DefaultAccount updatedAccount = new DefaultAccount(accountId, "extKey1338", "myemail1337@glam.com",
- "John Smith", 4, Currency.USD, 15, null,
- null, null, null, null, null, null, null, null, null, null,
- false, false);
+ final DefaultAccount updatedAccount = new DefaultAccount(accountId, "extKey1338", "myemail1337@glam.com",
+ "John Smith", 4, Currency.USD, 15, null,
+ null, null, null, null, null, null, null, null, null, null,
+ false, false);
accountDao.update(updatedAccount, context);
}
@@ -400,7 +402,7 @@ public class TestAccountDao extends AccountDaoTestBase {
List<AccountEmail> emails = new ArrayList<AccountEmail>();
// generate random account id
- UUID accountId = UUID.randomUUID();
+ final UUID accountId = UUID.randomUUID();
// add a new e-mail
final AccountEmail email = new DefaultAccountEmail(accountId, "test@gmail.com");
@@ -413,7 +415,7 @@ public class TestAccountDao extends AccountDaoTestBase {
verifyAccountEmailAuditAndHistoryCount(accountId, 1);
// update e-mail
- AccountEmail updatedEmail = new DefaultAccountEmail(email, "test2@gmail.com");
+ final AccountEmail updatedEmail = new DefaultAccountEmail(email, "test2@gmail.com");
emails.clear();
emails.add(updatedEmail);
accountEmailDao.saveEmails(accountId, emails, context);
@@ -433,8 +435,46 @@ public class TestAccountDao extends AccountDaoTestBase {
verifyAccountEmailAuditAndHistoryCount(accountId, 4);
}
- private void verifyAccountEmailAuditAndHistoryCount(UUID accountId, int expectedCount) {
- Handle handle = dbi.open();
+ @Test
+ public void testAddAndRemoveAccountEmail() {
+ 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).size(), 0);
+
+ // Add a new e-mail
+ final AccountEmail accountEmail1 = new DefaultAccountEmail(accountId, email1);
+ accountEmailDao.addEmail(accountId, accountEmail1, context);
+ final List<AccountEmail> firstEmails = accountEmailDao.getEmails(accountId);
+ 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, context);
+ final List<AccountEmail> secondEmails = accountEmailDao.getEmails(accountId);
+ assertEquals(secondEmails.size(), 2);
+ assertTrue(secondEmails.get(0).getAccountId().equals(accountId));
+ assertTrue(secondEmails.get(1).getAccountId().equals(accountId));
+ assertTrue(secondEmails.get(0).getEmail().equals(email1) || secondEmails.get(0).getEmail().equals(email2));
+ assertTrue(secondEmails.get(1).getEmail().equals(email1) || secondEmails.get(1).getEmail().equals(email2));
+
+ // Delete the first e-mail
+ accountEmailDao.removeEmail(accountId, accountEmail1, context);
+ final List<AccountEmail> thirdEmails = accountEmailDao.getEmails(accountId);
+ assertEquals(thirdEmails.size(), 1);
+ assertEquals(thirdEmails.get(0).getAccountId(), accountId);
+ assertEquals(thirdEmails.get(0).getEmail(), email2);
+
+ // Verify that history and audit contain three entries (2 inserts and one delete)
+ verifyAccountEmailAuditAndHistoryCount(accountId, 3);
+ }
+
+ private void verifyAccountEmailAuditAndHistoryCount(final UUID accountId, final int expectedCount) {
+ final Handle handle = dbi.open();
// verify audit
StringBuilder sb = new StringBuilder();
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockAccountUserApi.java b/analytics/src/test/java/com/ning/billing/analytics/MockAccountUserApi.java
index a758681..51f2ae8 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockAccountUserApi.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockAccountUserApi.java
@@ -30,8 +30,6 @@ import com.ning.billing.account.api.DefaultAccount;
import com.ning.billing.account.api.MigrationAccountData;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.customfield.CustomField;
-import com.ning.billing.util.tag.TagDefinition;
public class MockAccountUserApi implements AccountUserApi {
private final AccountData account;
@@ -83,6 +81,16 @@ public class MockAccountUserApi implements AccountUserApi {
}
@Override
+ public void addEmail(final UUID accountId, final AccountEmail email, final CallContext context) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void removeEmail(final UUID accountId, final AccountEmail email, final CallContext context) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public Account migrateAccount(MigrationAccountData data, final CallContext context)
throws AccountApiException {
throw new UnsupportedOperationException();
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 54d176f..85e03cf 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
@@ -50,4 +50,8 @@ public interface AccountUserApi {
public List<AccountEmail> getEmails(UUID accountId);
public void saveEmails(UUID accountId, List<AccountEmail> emails, CallContext context);
+
+ public void addEmail(UUID accountId, AccountEmail email, CallContext context);
+
+ public void removeEmail(UUID accountId, AccountEmail email, CallContext context);
}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountEmailJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountEmailJson.java
new file mode 100644
index 0000000..9a0d074
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountEmailJson.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2010-2012 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.jaxrs.json;
+
+import java.util.UUID;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.ning.billing.account.api.AccountEmail;
+
+public class AccountEmailJson {
+ private final String accountId;
+ private final String email;
+
+ @JsonCreator
+ public AccountEmailJson(@JsonProperty("accountId") final String accountId, @JsonProperty("email") final String email) {
+ this.accountId = accountId;
+ this.email = email;
+ }
+
+ public String getAccountId() {
+ return accountId;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public AccountEmail toAccountEmail() {
+ final UUID accountEmailId = UUID.randomUUID();
+
+ return new AccountEmail() {
+ @Override
+ public UUID getAccountId() {
+ return UUID.fromString(accountId);
+ }
+
+ @Override
+ public String getEmail() {
+ return email;
+ }
+
+ @Override
+ public UUID getId() {
+ return accountEmailId;
+ }
+ };
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("AccountEmailJson");
+ sb.append("{accountId='").append(accountId).append('\'');
+ sb.append(", email='").append(email).append('\'');
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ final AccountEmailJson that = (AccountEmailJson) o;
+
+ if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) {
+ return false;
+ }
+ if (email != null ? !email.equals(that.email) : that.email != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = accountId != null ? accountId.hashCode() : 0;
+ result = 31 * result + (email != null ? email.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
index eb633cf..2249eb6 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
@@ -46,6 +46,7 @@ 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.AccountUserApi;
import com.ning.billing.entitlement.api.timeline.BundleTimeline;
import com.ning.billing.entitlement.api.timeline.EntitlementRepairException;
@@ -54,6 +55,7 @@ import com.ning.billing.entitlement.api.user.EntitlementUserApi;
import com.ning.billing.entitlement.api.user.SubscriptionBundle;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.jaxrs.json.AccountEmailJson;
import com.ning.billing.jaxrs.json.AccountJson;
import com.ning.billing.jaxrs.json.AccountTimelineJson;
import com.ning.billing.jaxrs.json.BundleJsonNoSubscriptions;
@@ -80,6 +82,7 @@ public class AccountResource extends JaxRsResourceBase {
private static final String ID_PARAM_NAME = "accountId";
private static final String CUSTOM_FIELD_URI = JaxrsResource.CUSTOM_FIELDS + "/{" + ID_PARAM_NAME + ":" + UUID_PATTERN + "}";
private static final String TAG_URI = JaxrsResource.TAGS + "/{" + ID_PARAM_NAME + ":" + UUID_PATTERN + "}";
+ private static final String EMAIL_URI = JaxrsResource.EMAILS + "/{" + ID_PARAM_NAME + ":" + UUID_PATTERN + "}";
private final AccountUserApi accountApi;
private final EntitlementUserApi entitlementApi;
@@ -259,8 +262,7 @@ public class AccountResource extends JaxRsResourceBase {
}
}
-
- /**
+ /*
* ************************** PAYMENTS ********************************
*/
@@ -313,7 +315,6 @@ public class AccountResource extends JaxRsResourceBase {
}
}
-
@GET
@Path("/{accountId:\\w+-\\w+-\\w+-\\w+-\\w+}/" + PAYMENT_METHODS)
@Produces(APPLICATION_JSON)
@@ -332,9 +333,10 @@ public class AccountResource extends JaxRsResourceBase {
}
}
- /**
- * ************************* TAGS *****************************
+ /*
+ * ************************* CUSTOM FIELDS *****************************
*/
+
@GET
@Path(CUSTOM_FIELD_URI)
@Produces(APPLICATION_JSON)
@@ -368,6 +370,10 @@ public class AccountResource extends JaxRsResourceBase {
context.createContext(createdBy, reason, comment));
}
+ /*
+ * ************************* TAGS *****************************
+ */
+
@GET
@Path(TAG_URI)
@Produces(APPLICATION_JSON)
@@ -402,6 +408,71 @@ public class AccountResource extends JaxRsResourceBase {
context.createContext(createdBy, reason, comment));
}
+ /*
+ * ************************* EMAILS *****************************
+ */
+
+ @GET
+ @Path(EMAIL_URI)
+ @Produces(APPLICATION_JSON)
+ public Response getEmails(@PathParam(ID_PARAM_NAME) final String id) {
+ final UUID accountId = UUID.fromString(id);
+ final List<AccountEmail> emails = accountApi.getEmails(accountId);
+
+ final List<AccountEmailJson> emailsJson = new ArrayList<AccountEmailJson>();
+ for (final AccountEmail email : emails) {
+ emailsJson.add(new AccountEmailJson(email.getAccountId().toString(), email.getEmail()));
+ }
+ return Response.status(Response.Status.OK).entity(emailsJson).build();
+ }
+
+ @POST
+ @Path(EMAIL_URI)
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ public Response addEmail(final AccountEmailJson json,
+ @PathParam(ID_PARAM_NAME) final String id,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment) {
+ try {
+ final UUID accountId = UUID.fromString(id);
+
+ // Make sure the account exist or we will confuse the history and auditing code
+ if (accountApi.getAccountById(accountId) == null) {
+ return Response.status(Response.Status.BAD_REQUEST).entity("Account id " + accountId + " does not exist").build();
+ }
+
+ accountApi.addEmail(accountId, json.toAccountEmail(), context.createContext(createdBy, reason, comment));
+
+ return uriBuilder.buildResponse(AccountResource.class, "getEmails", json.getAccountId());
+ } catch (RuntimeException e) {
+ return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
+ } catch (AccountApiException e) {
+ return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
+ }
+ }
+
+ @DELETE
+ @Path(EMAIL_URI + "/{email}")
+ @Produces(APPLICATION_JSON)
+ public Response removeEmail(@PathParam(ID_PARAM_NAME) final String id,
+ @PathParam("email") final String email,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment) {
+ try {
+ final UUID accountId = UUID.fromString(id);
+ final AccountEmailJson accountEmailJson = new AccountEmailJson(id, email);
+ final AccountEmail accountEmail = accountEmailJson.toAccountEmail();
+ accountApi.removeEmail(accountId, accountEmail, context.createContext(createdBy, reason, comment));
+
+ return Response.status(Response.Status.OK).build();
+ } catch (RuntimeException e) {
+ return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
+ }
+ }
+
@Override
protected ObjectType getObjectType() {
return ObjectType.ACCOUNT;
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
index 1d2e92a..8da72f2 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
@@ -88,6 +88,7 @@ public interface JaxrsResource {
public static final String TAGS = "tags";
public static final String CUSTOM_FIELDS = "custom_fields";
+ public static final String EMAILS = "emails";
public static final String CATALOG = "catalog";
public static final String CATALOG_PATH = PREFIX + "/" + CATALOG;
diff --git a/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestAccountEmailJson.java b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestAccountEmailJson.java
new file mode 100644
index 0000000..c9e55f8
--- /dev/null
+++ b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestAccountEmailJson.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2010-2012 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.jaxrs.json;
+
+import java.util.UUID;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ning.billing.account.api.AccountEmail;
+
+public class TestAccountEmailJson {
+ private static final ObjectMapper mapper = new ObjectMapper();
+
+ @Test(groups = "fast")
+ public void testJson() throws Exception {
+ final String accountId = UUID.randomUUID().toString();
+ final String email = UUID.randomUUID().toString();
+
+ final AccountEmailJson accountEmailJson = new AccountEmailJson(accountId, email);
+ Assert.assertEquals(accountEmailJson.getAccountId(), accountId);
+ Assert.assertEquals(accountEmailJson.getEmail(), email);
+
+ final String asJson = mapper.writeValueAsString(accountEmailJson);
+ Assert.assertEquals(asJson, "{\"accountId\":\"" + accountId + "\"," +
+ "\"email\":\"" + email + "\"}");
+
+ final AccountEmailJson fromJson = mapper.readValue(asJson, AccountEmailJson.class);
+ Assert.assertEquals(fromJson, accountEmailJson);
+ }
+
+ @Test(groups = "fast")
+ public void testToAccountEmail() throws Exception {
+ final String accountId = UUID.randomUUID().toString();
+ final String email = UUID.randomUUID().toString();
+
+ final AccountEmailJson accountEmailJson = new AccountEmailJson(accountId, email);
+ Assert.assertEquals(accountEmailJson.getAccountId(), accountId);
+ Assert.assertEquals(accountEmailJson.getEmail(), email);
+
+ final AccountEmail accountEmail = accountEmailJson.toAccountEmail();
+ Assert.assertEquals(accountEmail.getAccountId().toString(), accountId);
+ Assert.assertEquals(accountEmail.getEmail(), email);
+ }
+}
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 9463923..e568b61 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
@@ -95,4 +95,14 @@ public class BlockingAccountUserApi implements AccountUserApi {
public void saveEmails(final UUID accountId, final List<AccountEmail> emails, final CallContext context) {
userApi.saveEmails(accountId, emails, context);
}
+
+ @Override
+ public void addEmail(final UUID accountId, final AccountEmail email, final CallContext context) {
+ userApi.addEmail(accountId, email, context);
+ }
+
+ @Override
+ public void removeEmail(final UUID accountId, final AccountEmail email, final CallContext context) {
+ userApi.removeEmail(accountId, email, context);
+ }
}
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestAccountEmail.java b/server/src/test/java/com/ning/billing/jaxrs/TestAccountEmail.java
new file mode 100644
index 0000000..428a352
--- /dev/null
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestAccountEmail.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2010-2012 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.jaxrs;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.ning.billing.jaxrs.json.AccountEmailJson;
+import com.ning.billing.jaxrs.json.AccountJson;
+import com.ning.billing.jaxrs.resources.JaxrsResource;
+import com.ning.http.client.Response;
+
+import static org.testng.Assert.assertEquals;
+
+public class TestAccountEmail extends TestJaxrsBase {
+ @Test(groups = "slow", enabled = true)
+ public void testAddAndRemoveAccountEmail() throws Exception {
+ final AccountJson input = createAccount(UUID.randomUUID().toString(), UUID.randomUUID().toString(), UUID.randomUUID().toString());
+ final String accountId = input.getAccountId();
+
+ final String email1 = UUID.randomUUID().toString();
+ final String email2 = UUID.randomUUID().toString();
+ final AccountEmailJson accountEmailJson1 = new AccountEmailJson(accountId, email1);
+ final AccountEmailJson accountEmailJson2 = new AccountEmailJson(accountId, email2);
+
+ final String baseUri = JaxrsResource.ACCOUNTS_PATH + "/" + JaxrsResource.EMAILS + "/" + accountId;
+
+ // Verify the initial state
+ final Response firstResponse = doGet(baseUri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(firstResponse.getStatusCode(), javax.ws.rs.core.Response.Status.OK.getStatusCode());
+ final List<AccountEmailJson> firstEmails = mapper.readValue(firstResponse.getResponseBody(), new TypeReference<List<AccountEmailJson>>() {});
+ Assert.assertEquals(firstEmails.size(), 0);
+
+ // Add an email
+ final String firstEmailString = mapper.writeValueAsString(accountEmailJson1);
+ final Response secondResponse = doPost(baseUri, firstEmailString, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ assertEquals(secondResponse.getStatusCode(), javax.ws.rs.core.Response.Status.CREATED.getStatusCode());
+
+ // Verify we can retrieve it
+ final Response thirdResponse = doGet(baseUri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(thirdResponse.getStatusCode(), javax.ws.rs.core.Response.Status.OK.getStatusCode());
+ final List<AccountEmailJson> secondEmails = mapper.readValue(thirdResponse.getResponseBody(), new TypeReference<List<AccountEmailJson>>() {});
+ Assert.assertEquals(secondEmails.size(), 1);
+ Assert.assertEquals(secondEmails.get(0).getAccountId(), accountId);
+ Assert.assertEquals(secondEmails.get(0).getEmail(), email1);
+
+ // Add another email
+ final String secondEmailString = mapper.writeValueAsString(accountEmailJson2);
+ final Response thridResponse = doPost(baseUri, secondEmailString, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ assertEquals(thridResponse.getStatusCode(), javax.ws.rs.core.Response.Status.CREATED.getStatusCode());
+
+ // Verify we can retrieve both
+ final Response fourthResponse = doGet(baseUri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(fourthResponse.getStatusCode(), javax.ws.rs.core.Response.Status.OK.getStatusCode());
+ final List<AccountEmailJson> thirdEmails = mapper.readValue(fourthResponse.getResponseBody(), new TypeReference<List<AccountEmailJson>>() {});
+ Assert.assertEquals(thirdEmails.size(), 2);
+ Assert.assertEquals(thirdEmails.get(0).getAccountId(), accountId);
+ Assert.assertEquals(thirdEmails.get(1).getAccountId(), accountId);
+ Assert.assertTrue(thirdEmails.get(0).getEmail().equals(email1) || thirdEmails.get(0).getEmail().equals(email2));
+ Assert.assertTrue(thirdEmails.get(1).getEmail().equals(email1) || thirdEmails.get(1).getEmail().equals(email2));
+
+ // Delete the first email
+ final Response fifthResponse = doDelete(baseUri + "/" + email1, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ assertEquals(fifthResponse.getStatusCode(), javax.ws.rs.core.Response.Status.OK.getStatusCode());
+
+ // Verify it has been deleted
+ final Response sixthResponse = doGet(baseUri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(sixthResponse.getStatusCode(), javax.ws.rs.core.Response.Status.OK.getStatusCode());
+ final List<AccountEmailJson> fourthEmails = mapper.readValue(sixthResponse.getResponseBody(), new TypeReference<List<AccountEmailJson>>() {});
+ Assert.assertEquals(fourthEmails.size(), 1);
+ Assert.assertEquals(fourthEmails.get(0).getAccountId(), accountId);
+ Assert.assertEquals(fourthEmails.get(0).getEmail(), email2);
+ }
+}
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
index 7df9c44..6c9ac05 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
@@ -527,6 +527,7 @@ public class TestJaxrsBase {
String country = "France";
String phone = "81 53 26 56";
+ // Note: the accountId payload is ignored on account creation
AccountJson accountJson = new AccountJson(accountId, name, length, externalKey, email, billCycleDay, currency, null, timeZone, address1, address2, company, state, country, phone);
return accountJson;
}
diff --git a/util/src/main/java/com/ning/billing/util/dao/AuditedCollectionDaoBase.java b/util/src/main/java/com/ning/billing/util/dao/AuditedCollectionDaoBase.java
index 3cf6620..0b61692 100644
--- a/util/src/main/java/com/ning/billing/util/dao/AuditedCollectionDaoBase.java
+++ b/util/src/main/java/com/ning/billing/util/dao/AuditedCollectionDaoBase.java
@@ -47,74 +47,94 @@ public abstract class AuditedCollectionDaoBase<T extends Entity, V> implements A
*/
protected abstract V getEquivalenceObjectFor(T obj);
+ /**
+ * Update all entities of given type objectType for the given objectId, e.g. if T is AccountEmail, objectId will
+ * represent an account id and objectType will be ACCOUNT_EMAIL.
+ * <p/>
+ * This will add and delete entities as needed.
+ *
+ * @param transactionalDao the current dao (in the transaction)
+ * @param objectId the parent object id
+ * @param objectType the entity object type
+ * @param newEntities the final list of entities
+ * @param context the current content
+ */
@Override
public void saveEntitiesFromTransaction(Transmogrifier transactionalDao, UUID objectId, ObjectType objectType, List<T> newEntities, CallContext context) {
UpdatableEntityCollectionSqlDao<T> dao = transmogrifyDao(transactionalDao);
- // get list of existing entities
- List<T> currentEntities = dao.load(objectId.toString(), objectType);
+ // Get list of all existing entities for this parent object, e.g. find all email addresses for this account
+ final List<T> currentEntities = dao.load(objectId.toString(), objectType);
- Map<V, T> currentObjs = new HashMap<V, T>(currentEntities.size());
- Map<V, T> updatedObjs = new HashMap<V, T>(newEntities.size());
+ // Compute the list of objects to add, remove and/or update
+ final Map<V, T> currentObjs = new HashMap<V, T>(currentEntities.size());
+ final Map<V, T> updatedObjs = new HashMap<V, T>(newEntities.size());
- for (T currentObj : currentEntities) {
+ for (final T currentObj : currentEntities) {
currentObjs.put(getEquivalenceObjectFor(currentObj), currentObj);
}
- for (T updatedObj : newEntities) {
+ for (final T updatedObj : newEntities) {
updatedObjs.put(getEquivalenceObjectFor(updatedObj), updatedObj);
}
- Set<V> equivToRemove = Sets.difference(currentObjs.keySet(), updatedObjs.keySet());
- Set<V> equivToAdd = Sets.difference(updatedObjs.keySet(), currentObjs.keySet());
- Set<V> equivToCheckForUpdate = Sets.intersection(updatedObjs.keySet(), currentObjs.keySet());
+ final Set<V> equivToRemove = Sets.difference(currentObjs.keySet(), updatedObjs.keySet());
+ final Set<V> equivToAdd = Sets.difference(updatedObjs.keySet(), currentObjs.keySet());
+ final Set<V> equivToCheckForUpdate = Sets.intersection(updatedObjs.keySet(), currentObjs.keySet());
- List<T> objsToAdd = new ArrayList<T>(equivToAdd.size());
- List<T> objsToRemove = new ArrayList<T>(equivToRemove.size());
- List<T> objsToUpdate = new ArrayList<T>(equivToCheckForUpdate.size());
+ final List<T> objsToAdd = new ArrayList<T>(equivToAdd.size());
+ final List<T> objsToRemove = new ArrayList<T>(equivToRemove.size());
+ final List<T> objsToUpdate = new ArrayList<T>(equivToCheckForUpdate.size());
- for (V equiv : equivToAdd) {
+ for (final V equiv : equivToAdd) {
objsToAdd.add(updatedObjs.get(equiv));
}
- for (V equiv : equivToRemove) {
+ for (final V equiv : equivToRemove) {
objsToRemove.add(currentObjs.get(equiv));
}
- for (V equiv : equivToCheckForUpdate) {
- T currentObj = currentObjs.get(equiv);
- T updatedObj = updatedObjs.get(equiv);
+ for (final V equiv : equivToCheckForUpdate) {
+ final T currentObj = currentObjs.get(equiv);
+ final T updatedObj = updatedObjs.get(equiv);
if (!currentObj.equals(updatedObj)) {
objsToUpdate.add(updatedObj);
}
}
+ // Perform the inserts
if (objsToAdd.size() != 0) {
dao.insertFromTransaction(objectId.toString(), objectType, objsToAdd, context);
}
+ // Perform the updates
if (objsToUpdate.size() != 0) {
dao.updateFromTransaction(objectId.toString(), objectType, objsToUpdate, context);
}
- // get all custom entities (including those that are about to be deleted) from the database in order to get the record ids
- List<Mapper<UUID, Long>> recordIds = dao.getRecordIds(objectId.toString(), objectType);
- Map<UUID, Long> recordIdMap = convertToHistoryMap(recordIds);
+ // Find all pairs <entity id, record id> (including those that are about to be deleted) for this parent object
+ final List<Mapper<UUID, Long>> recordIds = dao.getRecordIds(objectId.toString(), objectType);
+ // Flip the map to look up the record id associated with an entity id
+ final Map<UUID, Long> recordIdMap = convertToHistoryMap(recordIds);
+ // Perform the deletes
if (objsToRemove.size() != 0) {
dao.deleteFromTransaction(objectId.toString(), objectType, objsToRemove, context);
}
- List<EntityHistory<T>> entityHistories = new ArrayList<EntityHistory<T>>();
+ // Create the history objects
+ final List<EntityHistory<T>> entityHistories = new ArrayList<EntityHistory<T>>();
entityHistories.addAll(convertToHistory(objsToAdd, recordIdMap, ChangeType.INSERT));
entityHistories.addAll(convertToHistory(objsToUpdate, recordIdMap, ChangeType.UPDATE));
entityHistories.addAll(convertToHistory(objsToRemove, recordIdMap, ChangeType.DELETE));
- Long maxHistoryRecordId = dao.getMaxHistoryRecordId();
+ final Long maxHistoryRecordId = dao.getMaxHistoryRecordId();
+ // Save the records in the history table
dao.addHistoryFromTransaction(objectId.toString(), objectType, entityHistories, context);
- // have to fetch history record ids to update audit log
- List<Mapper<Long, Long>> historyRecordIds = dao.getHistoryRecordIds(maxHistoryRecordId);
- Map<Long, Long> historyRecordIdMap = convertToAuditMap(historyRecordIds);
- List<EntityAudit> entityAudits = convertToAudits(entityHistories, historyRecordIdMap);
+ // We have to fetch history record ids to update audit log
+ final List<Mapper<Long, Long>> historyRecordIds = dao.getHistoryRecordIds(maxHistoryRecordId);
+ final Map<Long, Long> historyRecordIdMap = convertToAuditMap(historyRecordIds);
+ final List<EntityAudit> entityAudits = convertToAudits(entityHistories, historyRecordIdMap);
+ // Save an entry in the audit log
dao.insertAuditFromTransaction(entityAudits, context);
}
@@ -167,11 +187,12 @@ public abstract class AuditedCollectionDaoBase<T extends Entity, V> implements A
return audits;
}
- protected Map<UUID, Long> convertToHistoryMap(List<Mapper<UUID, Long>> recordIds) {
- Map<UUID, Long> recordIdMap = new HashMap<UUID, Long>();
- for (Mapper<UUID, Long> recordId : recordIds) {
+ protected Map<UUID, Long> convertToHistoryMap(final List<Mapper<UUID, Long>> recordIds) {
+ final Map<UUID, Long> recordIdMap = new HashMap<UUID, Long>();
+ for (final Mapper<UUID, Long> recordId : recordIds) {
recordIdMap.put(recordId.getKey(), recordId.getValue());
}
+
return recordIdMap;
}
@@ -184,7 +205,10 @@ public abstract class AuditedCollectionDaoBase<T extends Entity, V> implements A
}
protected abstract TableName getTableName();
+
protected abstract UpdatableEntityCollectionSqlDao<T> transmogrifyDao(Transmogrifier transactionalDao);
+
protected abstract UpdatableEntityCollectionSqlDao<T> getSqlDao();
+
protected abstract String getKey(T entity);
}
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 d3a01d5..2ab74f6 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
@@ -16,19 +16,19 @@
package com.ning.billing.util.dao;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.entity.Entity;
+import java.util.List;
+
import org.skife.jdbi.v2.sqlobject.SqlBatch;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
-import java.util.List;
-import java.util.UUID;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.entity.Entity;
public interface CollectionHistorySqlDao<T extends Entity> {
- @SqlBatch(transactional = false)
+ @SqlBatch
public void addHistoryFromTransaction(String objectId, ObjectType objectType,
- List<EntityHistory<T>> histories,
- CallContext context);
+ List<EntityHistory<T>> histories,
+ CallContext context);
@SqlUpdate
public void addHistoryFromTransaction(String objectId, ObjectType objectType,
diff --git a/util/src/main/java/com/ning/billing/util/entity/collection/dao/EntityCollectionSqlDao.java b/util/src/main/java/com/ning/billing/util/entity/collection/dao/EntityCollectionSqlDao.java
index e74a64a..2decb16 100644
--- a/util/src/main/java/com/ning/billing/util/entity/collection/dao/EntityCollectionSqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/entity/collection/dao/EntityCollectionSqlDao.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2010-2012 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
@@ -38,16 +38,17 @@ import com.ning.billing.util.entity.Entity;
/**
* provides consistent semantics for entity collections
* note: this is intended to be extended by an interface which provides @ExternalizedSqlViaStringTemplate3 and mappers
+ *
* @param <T>
*/
public interface EntityCollectionSqlDao<T extends Entity> {
- @SqlBatch(transactional=false)
+ @SqlBatch
public void insertFromTransaction(@Bind("objectId") final String objectId,
@ObjectTypeBinder final ObjectType objectType,
@BindBean final Collection<T> entities,
@CallContextBinder final CallContext context);
- @SqlBatch(transactional=false)
+ @SqlBatch
public void deleteFromTransaction(@Bind("objectId") final String objectId,
@ObjectTypeBinder final ObjectType objectType,
@BindBean final Collection<T> entities,
diff --git a/util/src/main/java/com/ning/billing/util/entity/collection/dao/UpdatableEntityCollectionSqlDao.java b/util/src/main/java/com/ning/billing/util/entity/collection/dao/UpdatableEntityCollectionSqlDao.java
index 0079008..554120f 100644
--- a/util/src/main/java/com/ning/billing/util/entity/collection/dao/UpdatableEntityCollectionSqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/entity/collection/dao/UpdatableEntityCollectionSqlDao.java
@@ -40,7 +40,7 @@ import com.ning.billing.util.entity.Entity;
public interface UpdatableEntityCollectionSqlDao<T extends Entity> extends EntityCollectionSqlDao<T>,
CollectionHistorySqlDao<T>,
AuditSqlDao, CloseMe, Transmogrifier {
- @SqlBatch(transactional=false)
+ @SqlBatch
public void updateFromTransaction(@Bind("objectId") final String objectId,
@ObjectTypeBinder final ObjectType objectType,
@BindBean final Collection<T> entities,