killbill-aplcache
Changes
.idea/libraries/Maven__com_ning_billing_plugin_killbill_plugin_api_notification_0_4_0.xml 13(+0 -13)
.travis.yml 2(+1 -1)
osgi-bundles/bundles/jruby/src/main/java/com/ning/billing/osgi/bundles/jruby/JRubyPaymentPlugin.java 7(+4 -3)
osgi-bundles/tests/beatrix/src/test/java/com/ning/billing/osgi/bundles/test/TestPaymentPluginApi.java 31(+29 -2)
osgi-bundles/tests/payment/src/test/java/com/ning/billing/osgi/bundles/test/TestPaymentPluginApi.java 46(+44 -2)
payment/src/main/java/com/ning/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java 9(+6 -3)
pom.xml 2(+1 -1)
Details
.travis.yml 2(+1 -1)
diff --git a/.travis.yml b/.travis.yml
index add54e4..6283386 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,7 +5,7 @@ install: mvn install -DskipTests=true
notifications:
email:
- - killbilling-dev@googlegroups.com
+ - kill-bill-commits@googlegroups.com
jdk:
- openjdk6
diff --git a/account/src/main/java/com/ning/billing/account/api/svcs/DefaultAccountInternalApi.java b/account/src/main/java/com/ning/billing/account/api/svcs/DefaultAccountInternalApi.java
index 5f434c7..486057d 100644
--- a/account/src/main/java/com/ning/billing/account/api/svcs/DefaultAccountInternalApi.java
+++ b/account/src/main/java/com/ning/billing/account/api/svcs/DefaultAccountInternalApi.java
@@ -26,6 +26,7 @@ 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.AccountInternalApi;
import com.ning.billing.account.api.DefaultAccount;
import com.ning.billing.account.api.DefaultAccountEmail;
import com.ning.billing.account.dao.AccountDao;
@@ -33,11 +34,13 @@ import com.ning.billing.account.dao.AccountEmailModelDao;
import com.ning.billing.account.dao.AccountModelDao;
import com.ning.billing.callcontext.InternalCallContext;
import com.ning.billing.callcontext.InternalTenantContext;
-import com.ning.billing.account.api.AccountInternalApi;
+import com.ning.billing.util.entity.DefaultPagination;
+import com.ning.billing.util.entity.Pagination;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterators;
public class DefaultAccountInternalApi implements AccountInternalApi {
@@ -64,6 +67,21 @@ public class DefaultAccountInternalApi implements AccountInternalApi {
}
@Override
+ public Pagination<Account> getAccounts(final Long offset, final Long limit, final InternalTenantContext context) {
+ final Pagination<AccountModelDao> accountModelDaos = accountDao.get(offset, limit, context);
+ return new DefaultPagination<Account>(accountModelDaos,
+ limit,
+ Iterators.<AccountModelDao, Account>transform(accountModelDaos.iterator(),
+ new Function<AccountModelDao, Account>() {
+ @Override
+ public Account apply(final AccountModelDao input) {
+ return new DefaultAccount(input);
+ }
+ }));
+
+ }
+
+ @Override
public void updateAccount(final String externalKey, final AccountData accountData,
final InternalCallContext context) throws AccountApiException {
final Account currentAccount = getAccountByKey(externalKey, context);
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 96d6191..d1ffe58 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
@@ -19,8 +19,6 @@ package com.ning.billing.account.api.user;
import java.util.List;
import java.util.UUID;
-import javax.annotation.Nullable;
-
import com.ning.billing.ErrorCode;
import com.ning.billing.account.api.Account;
import com.ning.billing.account.api.AccountApiException;
@@ -36,10 +34,13 @@ import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.CallContextFactory;
import com.ning.billing.util.callcontext.InternalCallContextFactory;
import com.ning.billing.util.callcontext.TenantContext;
+import com.ning.billing.util.entity.DefaultPagination;
+import com.ning.billing.util.entity.Pagination;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterators;
import com.google.inject.Inject;
public class DefaultAccountUserApi implements AccountUserApi {
@@ -90,25 +91,31 @@ public class DefaultAccountUserApi implements AccountUserApi {
}
@Override
- public List<Account> searchAccounts(final String searchKey, final TenantContext context) {
- final List<AccountModelDao> accountModelDaos = accountDao.searchAccounts(searchKey, internalCallContextFactory.createInternalTenantContext(context));
- return ImmutableList.<Account>copyOf(Collections2.transform(accountModelDaos, new Function<AccountModelDao, Account>() {
- @Override
- public Account apply(final AccountModelDao input) {
- return new DefaultAccount(input);
- }
- }));
+ public Pagination<Account> searchAccounts(final String searchKey, final Long offset, final Long limit, final TenantContext context) {
+ final Pagination<AccountModelDao> accountModelDaos = accountDao.searchAccounts(searchKey, offset, limit, internalCallContextFactory.createInternalTenantContext(context));
+ return new DefaultPagination<Account>(accountModelDaos,
+ limit,
+ Iterators.<AccountModelDao, Account>transform(accountModelDaos.iterator(),
+ new Function<AccountModelDao, Account>() {
+ @Override
+ public Account apply(final AccountModelDao input) {
+ return new DefaultAccount(input);
+ }
+ }));
}
@Override
- public List<Account> getAccounts(final TenantContext context) {
- final List<AccountModelDao> accountModelDaos = accountDao.get(internalCallContextFactory.createInternalTenantContext(context));
- return ImmutableList.<Account>copyOf(Collections2.transform(accountModelDaos, new Function<AccountModelDao, Account>() {
- @Override
- public Account apply(@Nullable final AccountModelDao input) {
- return new DefaultAccount(input);
- }
- }));
+ public Pagination<Account> getAccounts(final Long offset, final Long limit, final TenantContext context) {
+ final Pagination<AccountModelDao> accountModelDaos = accountDao.get(offset, limit, internalCallContextFactory.createInternalTenantContext(context));
+ return new DefaultPagination<Account>(accountModelDaos,
+ limit,
+ Iterators.<AccountModelDao, Account>transform(accountModelDaos.iterator(),
+ new Function<AccountModelDao, Account>() {
+ @Override
+ public Account apply(final AccountModelDao input) {
+ return new DefaultAccount(input);
+ }
+ }));
}
@Override
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 eb80519..fbe6bfc 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
@@ -23,13 +23,14 @@ import com.ning.billing.account.api.Account;
import com.ning.billing.account.api.AccountApiException;
import com.ning.billing.callcontext.InternalCallContext;
import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.util.entity.Pagination;
import com.ning.billing.util.entity.dao.EntityDao;
public interface AccountDao extends EntityDao<AccountModelDao, Account, AccountApiException> {
public AccountModelDao getAccountByKey(String key, InternalTenantContext context);
- public List<AccountModelDao> searchAccounts(String searchKey, InternalTenantContext context);
+ public Pagination<AccountModelDao> searchAccounts(String searchKey, Long offset, Long limit, InternalTenantContext context);
/**
* @throws AccountApiException when externalKey is null
@@ -49,6 +50,4 @@ public interface AccountDao extends EntityDao<AccountModelDao, Account, AccountA
public void removeEmail(AccountEmailModelDao email, InternalCallContext context);
public List<AccountEmailModelDao> getEmailsByAccountId(UUID accountId, InternalTenantContext context);
-
- public AccountModelDao getByRecordId(Long recordId, InternalCallContext context);
}
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 ab38c74..4cfd258 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,7 +16,7 @@
package com.ning.billing.account.dao;
-import java.util.List;
+import java.util.Iterator;
import java.util.UUID;
import org.skife.jdbi.v2.sqlobject.Bind;
@@ -24,11 +24,12 @@ import org.skife.jdbi.v2.sqlobject.BindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
import org.skife.jdbi.v2.sqlobject.customizers.Define;
+import org.skife.jdbi.v2.sqlobject.customizers.FetchSize;
import com.ning.billing.account.api.Account;
-import com.ning.billing.util.audit.ChangeType;
import com.ning.billing.callcontext.InternalCallContext;
import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.util.audit.ChangeType;
import com.ning.billing.util.entity.dao.Audited;
import com.ning.billing.util.entity.dao.EntitySqlDao;
import com.ning.billing.util.entity.dao.EntitySqlDaoStringTemplate;
@@ -41,8 +42,13 @@ public interface AccountSqlDao extends EntitySqlDao<AccountModelDao, Account> {
@BindBean final InternalTenantContext context);
@SqlQuery
- public List<AccountModelDao> searchAccounts(@Define("searchKey") final String searchKey,
- @BindBean final InternalTenantContext context);
+ // Magic value to force MySQL to stream from the database
+ // See http://dev.mysql.com/doc/refman/5.0/en/connector-j-reference-implementation-notes.html (ResultSet)
+ @FetchSize(Integer.MIN_VALUE)
+ public Iterator<AccountModelDao> searchAccounts(@Define("searchKey") final String searchKey,
+ @Bind("offset") final Long offset,
+ @Bind("rowCount") final Long rowCount,
+ @BindBean final InternalTenantContext context);
@SqlQuery
public UUID getIdFromKey(@Bind("externalKey") final String key,
diff --git a/account/src/main/java/com/ning/billing/account/dao/DefaultAccountDao.java b/account/src/main/java/com/ning/billing/account/dao/DefaultAccountDao.java
index b73d2a5..fca3684 100644
--- a/account/src/main/java/com/ning/billing/account/dao/DefaultAccountDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/DefaultAccountDao.java
@@ -16,6 +16,7 @@
package com.ning.billing.account.dao;
+import java.util.Iterator;
import java.util.List;
import java.util.UUID;
@@ -32,21 +33,23 @@ import com.ning.billing.account.api.user.DefaultAccountCreationEvent;
import com.ning.billing.account.api.user.DefaultAccountCreationEvent.DefaultAccountData;
import com.ning.billing.bus.api.PersistentBus;
import com.ning.billing.bus.api.PersistentBus.EventBusException;
-import com.ning.billing.util.audit.ChangeType;
-import com.ning.billing.util.cache.CacheControllerDispatcher;
import com.ning.billing.callcontext.InternalCallContext;
-import com.ning.billing.util.callcontext.InternalCallContextFactory;
import com.ning.billing.callcontext.InternalTenantContext;
import com.ning.billing.clock.Clock;
-import com.ning.billing.util.dao.NonEntityDao;
import com.ning.billing.entity.EntityPersistenceException;
+import com.ning.billing.events.AccountChangeInternalEvent;
+import com.ning.billing.events.AccountCreationInternalEvent;
+import com.ning.billing.util.audit.ChangeType;
+import com.ning.billing.util.cache.CacheControllerDispatcher;
+import com.ning.billing.util.callcontext.InternalCallContextFactory;
+import com.ning.billing.util.dao.NonEntityDao;
+import com.ning.billing.util.entity.DefaultPagination;
+import com.ning.billing.util.entity.Pagination;
import com.ning.billing.util.entity.dao.EntityDaoBase;
import com.ning.billing.util.entity.dao.EntitySqlDao;
import com.ning.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
import com.ning.billing.util.entity.dao.EntitySqlDaoTransactionalJdbiWrapper;
import com.ning.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
-import com.ning.billing.events.AccountChangeInternalEvent;
-import com.ning.billing.events.AccountCreationInternalEvent;
import com.google.inject.Inject;
@@ -104,13 +107,31 @@ public class DefaultAccountDao extends EntityDaoBase<AccountModelDao, Account, A
}
@Override
- public List<AccountModelDao> searchAccounts(final String searchKey, final InternalTenantContext context) {
- return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<AccountModelDao>>() {
+ public Pagination<AccountModelDao> searchAccounts(final String searchKey, final Long offset, final Long limit, final InternalTenantContext context) {
+ // Note: the connection will be busy as we stream the results out: hence we cannot use
+ // SQL_CALC_FOUND_ROWS / FOUND_ROWS on the actual query.
+ // We still need to know the actual number of results, mainly for the UI so that it knows if it needs to fetch
+ // more pages. To do that, we perform a dummy search query with SQL_CALC_FOUND_ROWS (but limit 1).
+ final Long count = transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Long>() {
@Override
- public List<AccountModelDao> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
- return entitySqlDaoWrapperFactory.become(AccountSqlDao.class).searchAccounts(searchKey, context);
+ public Long inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+ final AccountSqlDao accountSqlDao = entitySqlDaoWrapperFactory.become(AccountSqlDao.class);
+ final Iterator<AccountModelDao> dumbIterator = accountSqlDao.searchAccounts(searchKey, offset, 1L, context);
+ // Make sure to go through the results to close the connection
+ while (dumbIterator.hasNext()) {
+ dumbIterator.next();
+ }
+ return accountSqlDao.getFoundRows(context);
}
});
+
+ // We usually always want to wrap our queries in an EntitySqlDaoTransactionWrapper... except here.
+ // Since we want to stream the results out, we don't want to auto-commit when this method returns.
+ final AccountSqlDao accountSqlDao = transactionalSqlDao.onDemand(AccountSqlDao.class);
+ final Long totalCount = accountSqlDao.getCount(context);
+ final Iterator<AccountModelDao> results = accountSqlDao.searchAccounts(searchKey, offset, limit, context);
+
+ return new DefaultPagination<AccountModelDao>(offset, limit, count, totalCount, results);
}
@Override
@@ -236,13 +257,4 @@ public class DefaultAccountDao extends EntityDaoBase<AccountModelDao, Account, A
});
}
- @Override
- public AccountModelDao getByRecordId(final Long recordId, final InternalCallContext context) {
- return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<AccountModelDao>() {
- @Override
- public AccountModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
- return entitySqlDaoWrapperFactory.become(AccountSqlDao.class).getByRecordId(recordId, context);
- }
- });
- }
}
diff --git a/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg b/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg
index 630f1dc..328f846 100644
--- a/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg
+++ b/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg
@@ -86,22 +86,27 @@ getAccountByKey() ::= <<
where external_key = :externalKey <AND_CHECK_TENANT()>;
>>
-searchAccounts(searchKey) ::= <<
-select <allTableFields()>
-from accounts
-where name like ('%<searchKey>%') <AND_CHECK_TENANT()>
-union
-select <allTableFields()>
-from accounts
-where email like ('%<searchKey>%') <AND_CHECK_TENANT()>
-union
-select <allTableFields()>
-from accounts
-where external_key like ('%<searchKey>%') <AND_CHECK_TENANT()>
-union
-select <allTableFields()>
-from accounts
-where company_name like ('%<searchKey>%') <AND_CHECK_TENANT()>
+searchAccounts(searchKey, offset, rowCount) ::= <<
+select SQL_CALC_FOUND_ROWS <allTableFields()>
+from (
+ select <allTableFields()>
+ from accounts
+ where name like ('%<searchKey>%') <AND_CHECK_TENANT()>
+ union
+ select <allTableFields()>
+ from accounts
+ where email like ('%<searchKey>%') <AND_CHECK_TENANT()>
+ union
+ select <allTableFields()>
+ from accounts
+ where external_key like ('%<searchKey>%') <AND_CHECK_TENANT()>
+ union
+ select <allTableFields()>
+ from accounts
+ where company_name like ('%<searchKey>%') <AND_CHECK_TENANT()>
+) results
+order by results.record_id
+limit :offset, :rowCount
;
>>
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 b8b6161..3a3620a 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
@@ -36,14 +36,16 @@ import com.ning.billing.account.api.user.DefaultAccountCreationEvent.DefaultAcco
import com.ning.billing.bus.api.PersistentBus;
import com.ning.billing.bus.api.PersistentBus.EventBusException;
import com.ning.billing.callcontext.InternalCallContext;
-import com.ning.billing.util.callcontext.InternalCallContextFactory;
import com.ning.billing.callcontext.InternalTenantContext;
-import com.ning.billing.util.entity.dao.MockEntityDaoBase;
import com.ning.billing.events.AccountChangeInternalEvent;
+import com.ning.billing.util.callcontext.InternalCallContextFactory;
+import com.ning.billing.util.entity.DefaultPagination;
+import com.ning.billing.util.entity.Pagination;
+import com.ning.billing.util.entity.dao.MockEntityDaoBase;
import com.google.common.base.Predicate;
-import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
import com.google.inject.Inject;
public class MockAccountDao extends MockEntityDaoBase<AccountModelDao, Account, AccountApiException> implements AccountDao {
@@ -79,7 +81,7 @@ public class MockAccountDao extends MockEntityDaoBase<AccountModelDao, Account,
final long tenantRecordId = context == null ? InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID
: context.getTenantRecordId();
final AccountChangeInternalEvent changeEvent = new DefaultAccountChangeEvent(account.getId(), currentAccount, account,
- accountRecordId, tenantRecordId, UUID.randomUUID()
+ accountRecordId, tenantRecordId, UUID.randomUUID()
);
if (changeEvent.hasChanges()) {
try {
@@ -103,9 +105,9 @@ public class MockAccountDao extends MockEntityDaoBase<AccountModelDao, Account,
}
@Override
- public List<AccountModelDao> searchAccounts(final String searchKey, final InternalTenantContext context) {
+ public Pagination<AccountModelDao> searchAccounts(final String searchKey, final Long offset, final Long limit, final InternalTenantContext context) {
final List<AccountModelDao> results = new LinkedList<AccountModelDao>();
- for (final AccountModelDao account : get(context)) {
+ for (final AccountModelDao account : getAll(context)) {
if ((account.getName() != null && account.getName().contains(searchKey)) ||
(account.getEmail() != null && account.getEmail().contains(searchKey)) ||
(account.getExternalKey() != null && account.getExternalKey().contains(searchKey)) ||
@@ -113,7 +115,8 @@ public class MockAccountDao extends MockEntityDaoBase<AccountModelDao, Account,
results.add(account);
}
}
- return results;
+
+ return DefaultPagination.<AccountModelDao>build(offset, limit, results);
}
@Override
@@ -152,7 +155,7 @@ public class MockAccountDao extends MockEntityDaoBase<AccountModelDao, Account,
@Override
public List<AccountEmailModelDao> getEmailsByAccountId(final UUID accountId, final InternalTenantContext context) {
- return ImmutableList.<AccountEmailModelDao>copyOf(Collections2.filter(accountEmailSqlDao.get(context), new Predicate<AccountEmailModelDao>() {
+ return ImmutableList.<AccountEmailModelDao>copyOf(Iterables.<AccountEmailModelDao>filter(accountEmailSqlDao.getAll(context), new Predicate<AccountEmailModelDao>() {
@Override
public boolean apply(final AccountEmailModelDao input) {
return input.getAccountId().equals(accountId);
@@ -160,8 +163,4 @@ public class MockAccountDao extends MockEntityDaoBase<AccountModelDao, Account,
}));
}
- @Override
- public AccountModelDao getByRecordId(final Long recordId, final InternalCallContext context) {
- return null;
- }
}
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 d407232..9fd099b 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
@@ -44,17 +44,20 @@ import com.ning.billing.util.customfield.CustomField;
import com.ning.billing.util.customfield.StringCustomField;
import com.ning.billing.util.customfield.dao.CustomFieldModelDao;
import com.ning.billing.util.dao.TableName;
+import com.ning.billing.util.entity.Pagination;
import com.ning.billing.util.tag.DescriptiveTag;
import com.ning.billing.util.tag.Tag;
import com.ning.billing.util.tag.dao.TagDefinitionModelDao;
import com.ning.billing.util.tag.dao.TagModelDao;
+import com.google.common.collect.ImmutableList;
+
import static com.ning.billing.account.AccountTestUtils.checkAccountsEqual;
import static com.ning.billing.account.AccountTestUtils.createTestAccount;
public class TestAccountDao extends AccountTestSuiteWithEmbeddedDB {
- @Test(groups = "slow", description="Test Account: basic DAO calls")
+ @Test(groups = "slow", description = "Test Account: basic DAO calls")
public void testBasic() throws AccountApiException {
final AccountModelDao account = createTestAccount();
accountDao.create(account, internalCallContext);
@@ -68,7 +71,8 @@ public class TestAccountDao extends AccountTestSuiteWithEmbeddedDB {
checkAccountsEqual(retrievedAccount, account);
// Retrieve all
- final List<AccountModelDao> all = accountDao.get(internalCallContext);
+ final Pagination<AccountModelDao> allAccounts = accountDao.getAll(internalCallContext);
+ final List<AccountModelDao> all = ImmutableList.<AccountModelDao>copyOf(allAccounts);
Assert.assertNotNull(all);
Assert.assertEquals(all.size(), 1);
checkAccountsEqual(all.get(0), account);
@@ -80,7 +84,7 @@ public class TestAccountDao extends AccountTestSuiteWithEmbeddedDB {
}
// Simple test to ensure long phone numbers can be stored
- @Test(groups = "slow", description="Test Account DAO: long numbers")
+ @Test(groups = "slow", description = "Test Account DAO: long numbers")
public void testLongPhoneNumber() throws AccountApiException {
final AccountModelDao account = createTestAccount("123456789012345678901234");
accountDao.create(account, internalCallContext);
@@ -90,7 +94,7 @@ public class TestAccountDao extends AccountTestSuiteWithEmbeddedDB {
}
// Simple test to ensure excessively long phone numbers cannot be stored
- @Test(groups = "slow", description="Test Account DAO: very long numbers")
+ @Test(groups = "slow", description = "Test Account DAO: very long numbers")
public void testOverlyLongPhoneNumber() throws AccountApiException {
final AccountModelDao account = createTestAccount("12345678901234567890123456");
try {
@@ -101,7 +105,7 @@ public class TestAccountDao extends AccountTestSuiteWithEmbeddedDB {
}
}
- @Test(groups = "slow", description="Test Account DAO: custom fields")
+ @Test(groups = "slow", description = "Test Account DAO: custom fields")
public void testCustomFields() throws CustomFieldApiException {
final UUID accountId = UUID.randomUUID();
final String fieldName = UUID.randomUUID().toString().substring(0, 4);
@@ -118,7 +122,7 @@ public class TestAccountDao extends AccountTestSuiteWithEmbeddedDB {
Assert.assertEquals(customField.getFieldValue(), fieldValue);
}
- @Test(groups = "slow", description="Test Account DAO: tags")
+ @Test(groups = "slow", description = "Test Account DAO: tags")
public void testTags() throws TagApiException, TagDefinitionApiException {
final AccountModelDao account = createTestAccount();
final TagDefinitionModelDao tagDefinition = tagDefinitionDao.create(UUID.randomUUID().toString().substring(0, 4), UUID.randomUUID().toString(), internalCallContext);
@@ -132,7 +136,7 @@ public class TestAccountDao extends AccountTestSuiteWithEmbeddedDB {
Assert.assertEquals(tags.get(0).getObjectType(), ObjectType.ACCOUNT);
}
- @Test(groups = "slow", description="Test Account DAO: retrieve by externalKey")
+ @Test(groups = "slow", description = "Test Account DAO: retrieve by externalKey")
public void testGetIdFromKey() throws AccountApiException {
final AccountModelDao account = createTestAccount();
accountDao.create(account, internalCallContext);
@@ -141,12 +145,12 @@ public class TestAccountDao extends AccountTestSuiteWithEmbeddedDB {
Assert.assertEquals(accountId, account.getId());
}
- @Test(groups = "slow", expectedExceptions = AccountApiException.class, description="Test Account DAO: retrieve by null externalKey throws an exception")
+ @Test(groups = "slow", expectedExceptions = AccountApiException.class, description = "Test Account DAO: retrieve by null externalKey throws an exception")
public void testGetIdFromKeyForNullKey() throws AccountApiException {
accountDao.getIdFromKey(null, internalCallContext);
}
- @Test(groups = "slow", description="Test Account DAO: basic update (1)")
+ @Test(groups = "slow", description = "Test Account DAO: basic update (1)")
public void testUpdate() throws Exception {
final AccountModelDao account = createTestAccount();
accountDao.create(account, internalCallContext);
@@ -163,7 +167,7 @@ public class TestAccountDao extends AccountTestSuiteWithEmbeddedDB {
checkAccountsEqual(retrievedAccount, updatedAccount);
}
- @Test(groups = "slow", description="Test Account DAO: payment method update")
+ @Test(groups = "slow", description = "Test Account DAO: payment method update")
public void testUpdatePaymentMethod() throws Exception {
final AccountModelDao account = createTestAccount();
accountDao.create(account, internalCallContext);
@@ -181,7 +185,7 @@ public class TestAccountDao extends AccountTestSuiteWithEmbeddedDB {
Assert.assertNull(newAccountWithPMNull.getPaymentMethodId());
}
- @Test(groups = "slow", description="Test Account DAO: basic update (2)")
+ @Test(groups = "slow", description = "Test Account DAO: basic update (2)")
public void testShouldBeAbleToUpdateSomeFields() throws Exception {
final AccountModelDao account = createTestAccount();
accountDao.create(account, internalCallContext);
@@ -196,7 +200,7 @@ public class TestAccountDao extends AccountTestSuiteWithEmbeddedDB {
checkAccountsEqual(retrievedAccount, newAccount);
}
- @Test(groups = "slow", description="Test Account DAO: BCD of 0")
+ @Test(groups = "slow", description = "Test Account DAO: BCD of 0")
public void testShouldBeAbleToHandleBCDOfZero() throws Exception {
final AccountModelDao account = createTestAccount(0);
accountDao.create(account, internalCallContext);
@@ -206,7 +210,7 @@ public class TestAccountDao extends AccountTestSuiteWithEmbeddedDB {
checkAccountsEqual(retrievedAccount, account);
}
- @Test(groups = "slow", description="Test Account DAO: duplicate emails throws an exception")
+ @Test(groups = "slow", description = "Test Account DAO: duplicate emails throws an exception")
public void testHandleDuplicateEmails() throws AccountApiException {
final UUID accountId = UUID.randomUUID();
final AccountEmail email = new DefaultAccountEmail(accountId, "test@gmail.com");
@@ -224,7 +228,7 @@ public class TestAccountDao extends AccountTestSuiteWithEmbeddedDB {
}
}
- @Test(groups = "slow", description="Test Account DAO: add and remove email")
+ @Test(groups = "slow", description = "Test Account DAO: add and remove email")
public void testAddRemoveAccountEmail() throws AccountApiException {
final UUID accountId = UUID.randomUUID();
@@ -247,7 +251,7 @@ public class TestAccountDao extends AccountTestSuiteWithEmbeddedDB {
Assert.assertEquals(accountDao.getEmailsByAccountId(accountId, internalCallContext).size(), 0);
}
- @Test(groups = "slow", description="Test Account DAO: add and remove multiple emails")
+ @Test(groups = "slow", description = "Test Account DAO: add and remove multiple emails")
public void testAddAndRemoveMultipleAccountEmails() throws AccountApiException {
final UUID accountId = UUID.randomUUID();
final String email1 = UUID.randomUUID().toString();
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountInternalApi.java b/api/src/main/java/com/ning/billing/account/api/AccountInternalApi.java
index 02ee51e..684ca30 100644
--- a/api/src/main/java/com/ning/billing/account/api/AccountInternalApi.java
+++ b/api/src/main/java/com/ning/billing/account/api/AccountInternalApi.java
@@ -21,6 +21,7 @@ import java.util.UUID;
import com.ning.billing.callcontext.InternalCallContext;
import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.util.entity.Pagination;
public interface AccountInternalApi {
@@ -30,6 +31,8 @@ public interface AccountInternalApi {
public Account getAccountByRecordId(Long recordId, InternalTenantContext context) throws AccountApiException;
+ public Pagination<Account> getAccounts(Long offset, Long limit, InternalTenantContext context);
+
public void updateAccount(String key, AccountData accountData, InternalCallContext context) throws AccountApiException;
public List<AccountEmail> getEmails(UUID accountId, InternalTenantContext context);
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
index 1615ee0..6d0feb4 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
@@ -32,8 +32,11 @@ import com.ning.billing.ErrorCode;
import com.ning.billing.ObjectType;
import com.ning.billing.account.api.Account;
import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.account.api.AccountInternalApi;
import com.ning.billing.bus.api.PersistentBus;
import com.ning.billing.bus.api.PersistentBus.EventBusException;
+import com.ning.billing.callcontext.InternalCallContext;
+import com.ning.billing.callcontext.InternalTenantContext;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.InvoiceDispatcher;
import com.ning.billing.invoice.api.Invoice;
@@ -48,20 +51,20 @@ import com.ning.billing.invoice.model.DefaultInvoice;
import com.ning.billing.invoice.model.ExternalChargeInvoiceItem;
import com.ning.billing.invoice.model.InvoiceItemFactory;
import com.ning.billing.invoice.template.HtmlInvoiceGenerator;
+import com.ning.billing.tag.TagInternalApi;
import com.ning.billing.util.api.TagApiException;
import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.callcontext.InternalCallContext;
import com.ning.billing.util.callcontext.InternalCallContextFactory;
-import com.ning.billing.callcontext.InternalTenantContext;
import com.ning.billing.util.callcontext.TenantContext;
-import com.ning.billing.account.api.AccountInternalApi;
-import com.ning.billing.tag.TagInternalApi;
+import com.ning.billing.util.entity.DefaultPagination;
+import com.ning.billing.util.entity.Pagination;
import com.ning.billing.util.tag.ControlTagType;
import com.ning.billing.util.tag.Tag;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterators;
import com.google.inject.Inject;
public class DefaultInvoiceUserApi implements InvoiceUserApi {
@@ -111,6 +114,21 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
}
@Override
+ public Pagination<Invoice> getInvoices(final Long offset, final Long limit, final TenantContext context) {
+ // Invoices will be shallow, i.e. won't contain items nor payments
+ final Pagination<InvoiceModelDao> invoiceModelDaos = dao.get(offset, limit, internalCallContextFactory.createInternalTenantContext(context));
+ return new DefaultPagination<Invoice>(invoiceModelDaos,
+ limit,
+ Iterators.<InvoiceModelDao, Invoice>transform(invoiceModelDaos.iterator(),
+ new Function<InvoiceModelDao, Invoice>() {
+ @Override
+ public Invoice apply(final InvoiceModelDao input) {
+ return new DefaultInvoice(input);
+ }
+ }));
+ }
+
+ @Override
public BigDecimal getAccountBalance(final UUID accountId, final TenantContext context) {
final BigDecimal result = dao.getAccountBalance(accountId, internalCallContextFactory.createInternalTenantContext(accountId, context));
return result == null ? BigDecimal.ZERO : result;
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
index c0ea740..c4ee207 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
@@ -32,6 +32,8 @@ import org.slf4j.LoggerFactory;
import com.ning.billing.ErrorCode;
import com.ning.billing.bus.api.PersistentBus;
import com.ning.billing.bus.api.PersistentBus.EventBusException;
+import com.ning.billing.callcontext.InternalCallContext;
+import com.ning.billing.callcontext.InternalTenantContext;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.clock.Clock;
import com.ning.billing.invoice.api.Invoice;
@@ -41,8 +43,6 @@ import com.ning.billing.invoice.api.InvoicePaymentType;
import com.ning.billing.invoice.api.user.DefaultInvoiceAdjustmentEvent;
import com.ning.billing.invoice.notification.NextBillingDatePoster;
import com.ning.billing.util.cache.CacheControllerDispatcher;
-import com.ning.billing.callcontext.InternalCallContext;
-import com.ning.billing.callcontext.InternalTenantContext;
import com.ning.billing.util.dao.NonEntityDao;
import com.ning.billing.util.entity.dao.EntityDaoBase;
import com.ning.billing.util.entity.dao.EntitySqlDao;
@@ -149,21 +149,6 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
}
@Override
- public List<InvoiceModelDao> get(final InternalTenantContext context) {
- return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<InvoiceModelDao>>() {
- @Override
- public List<InvoiceModelDao> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
- final InvoiceSqlDao invoiceDao = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class);
-
- final List<InvoiceModelDao> invoices = invoiceDao.get(context);
- invoiceDaoHelper.populateChildren(invoices, entitySqlDaoWrapperFactory, context);
-
- return invoices;
- }
- });
- }
-
- @Override
public InvoiceModelDao getById(final UUID invoiceId, final InternalTenantContext context) {
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<InvoiceModelDao>() {
@Override
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
index 4e6170c..8f2c668 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
@@ -26,22 +26,20 @@ import javax.annotation.Nullable;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.invoice.api.InvoiceApiException;
import com.ning.billing.callcontext.InternalCallContext;
import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.util.entity.dao.EntityDao;
-public interface InvoiceDao {
+public interface InvoiceDao extends EntityDao<InvoiceModelDao, Invoice, InvoiceApiException> {
void createInvoice(InvoiceModelDao invoice, List<InvoiceItemModelDao> invoiceItems,
List<InvoicePaymentModelDao> invoicePayments, boolean isRealInvoice, final Map<UUID, DateTime> callbackDateTimePerSubscriptions, InternalCallContext context);
- InvoiceModelDao getById(UUID id, InternalTenantContext context);
-
InvoiceModelDao getByNumber(Integer number, InternalTenantContext context) throws InvoiceApiException;
- List<InvoiceModelDao> get(InternalTenantContext context);
-
List<InvoiceModelDao> getInvoicesByAccount(InternalTenantContext context);
List<InvoiceModelDao> getInvoicesByAccount(LocalDate fromDate, InternalTenantContext context);
@@ -58,8 +56,6 @@ public interface InvoiceDao {
List<InvoiceModelDao> getUnpaidInvoicesByAccountId(UUID accountId, @Nullable LocalDate upToDate, InternalTenantContext context);
- void test(InternalTenantContext context);
-
// Include migrated invoices
List<InvoiceModelDao> getAllInvoicesByAccount(InternalTenantContext context);
@@ -165,9 +161,8 @@ public interface InvoiceDao {
void notifyOfPayment(InvoicePaymentModelDao invoicePayment, InternalCallContext context);
/**
- *
* @param accountId the account for which we need to rebalance the CBA
- * @param context the callcontext
+ * @param context the callcontext
*/
public void consumeExstingCBAOnAccountWithUnpaidInvoices(final UUID accountId, final InternalCallContext context);
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDaoHelper.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDaoHelper.java
index e34563f..9d07999 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDaoHelper.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDaoHelper.java
@@ -28,12 +28,12 @@ import javax.annotation.Nullable;
import org.joda.time.LocalDate;
import com.ning.billing.ErrorCode;
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.invoice.api.InvoiceApiException;
-import com.ning.billing.invoice.api.InvoiceItemType;
import com.ning.billing.callcontext.InternalCallContext;
import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.catalog.api.Currency;
import com.ning.billing.entity.EntityPersistenceException;
+import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoiceItemType;
import com.ning.billing.util.entity.dao.EntitySqlDao;
import com.ning.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
@@ -223,7 +223,6 @@ public class InvoiceDaoHelper {
transInvoiceItemDao.create(item, context);
}
-
public void populateChildren(final InvoiceModelDao invoice, final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final InternalTenantContext context) {
getInvoiceItemsWithinTransaction(invoice, entitySqlDaoWrapperFactory, context);
getInvoicePaymentsWithinTransaction(invoice, entitySqlDaoWrapperFactory, context);
@@ -271,6 +270,4 @@ public class InvoiceDaoHelper {
final List<InvoicePaymentModelDao> invoicePayments = invoicePaymentSqlDao.getPaymentsForInvoice(invoiceId, context);
invoice.addPayments(invoicePayments);
}
-
-
}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
index a36cf1e..5006858 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
@@ -30,17 +30,21 @@ import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import com.ning.billing.bus.api.PersistentBus;
+import com.ning.billing.callcontext.InternalCallContext;
+import com.ning.billing.callcontext.InternalTenantContext;
import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceApiException;
import com.ning.billing.invoice.api.user.DefaultInvoiceCreationEvent;
-import com.ning.billing.callcontext.InternalCallContext;
-import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.util.entity.DefaultPagination;
+import com.ning.billing.util.entity.Pagination;
+import com.ning.billing.util.entity.dao.MockEntityDaoBase;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.inject.Inject;
-public class MockInvoiceDao implements InvoiceDao {
+public class MockInvoiceDao extends MockEntityDaoBase<InvoiceModelDao, Invoice, InvoiceApiException> implements InvoiceDao {
private final PersistentBus eventBus;
private final Object monitor = new Object();
@@ -70,7 +74,7 @@ public class MockInvoiceDao implements InvoiceDao {
try {
eventBus.post(new DefaultInvoiceCreationEvent(invoice.getId(), invoice.getAccountId(),
InvoiceModelDaoHelper.getBalance(invoice), invoice.getCurrency(),
- context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken()));
+ context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken()));
} catch (PersistentBus.EventBusException ex) {
throw new RuntimeException(ex);
}
@@ -97,9 +101,9 @@ public class MockInvoiceDao implements InvoiceDao {
}
@Override
- public List<InvoiceModelDao> get(final InternalTenantContext context) {
+ public Pagination<InvoiceModelDao> getAll(final InternalTenantContext context) {
synchronized (monitor) {
- return new ArrayList<InvoiceModelDao>(invoices.values());
+ return new DefaultPagination<InvoiceModelDao>((long) invoices.values().size(), invoices.values().iterator());
}
}
@@ -124,7 +128,7 @@ public class MockInvoiceDao implements InvoiceDao {
synchronized (monitor) {
final UUID accountId = accountRecordIds.inverse().get(context.getAccountRecordId());
- for (final InvoiceModelDao invoice : get(context)) {
+ for (final InvoiceModelDao invoice : getAll(context)) {
if (accountId.equals(invoice.getAccountId()) && !invoice.getTargetDate().isBefore(fromDate) && !invoice.isMigrated()) {
invoicesForAccount.add(invoice);
}
@@ -195,7 +199,7 @@ public class MockInvoiceDao implements InvoiceDao {
public BigDecimal getAccountBalance(final UUID accountId, final InternalTenantContext context) {
BigDecimal balance = BigDecimal.ZERO;
- for (final InvoiceModelDao invoice : get(context)) {
+ for (final InvoiceModelDao invoice : getAll(context)) {
if (accountId.equals(invoice.getAccountId())) {
balance = balance.add(InvoiceModelDaoHelper.getBalance(invoice));
}
@@ -208,7 +212,7 @@ public class MockInvoiceDao implements InvoiceDao {
public List<InvoiceModelDao> getUnpaidInvoicesByAccountId(final UUID accountId, final LocalDate upToDate, final InternalTenantContext context) {
final List<InvoiceModelDao> unpaidInvoices = new ArrayList<InvoiceModelDao>();
- for (final InvoiceModelDao invoice : get(context)) {
+ for (final InvoiceModelDao invoice : getAll(context)) {
if (accountId.equals(invoice.getAccountId()) && (InvoiceModelDaoHelper.getBalance(invoice).compareTo(BigDecimal.ZERO) > 0) && !invoice.isMigrated()) {
unpaidInvoices.add(invoice);
}
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 95eed17..a210318 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
@@ -16,7 +16,10 @@
package com.ning.billing.jaxrs.resources;
+import java.io.IOException;
+import java.io.OutputStream;
import java.math.BigDecimal;
+import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -36,8 +39,10 @@ import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
+import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.StreamingOutput;
import javax.ws.rs.core.UriInfo;
import com.ning.billing.ErrorCode;
@@ -92,12 +97,14 @@ import com.ning.billing.util.audit.AuditLogsForPayments;
import com.ning.billing.util.audit.AuditLogsForRefunds;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.TenantContext;
+import com.ning.billing.util.entity.Pagination;
import com.ning.billing.util.tag.ControlTagType;
+import com.fasterxml.jackson.core.JsonGenerator;
import com.google.common.base.Function;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Collections2;
-import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -151,24 +158,61 @@ public class AccountResource extends JaxRsResourceBase {
}
@GET
+ @Path("/" + PAGINATION)
+ @Produces(APPLICATION_JSON)
+ public Response getAccounts(@QueryParam(QUERY_SEARCH_OFFSET) @DefaultValue("0") final Long offset,
+ @QueryParam(QUERY_SEARCH_LIMIT) @DefaultValue("100") final Long limit,
+ @QueryParam(QUERY_ACCOUNT_WITH_BALANCE) @DefaultValue("false") final Boolean accountWithBalance,
+ @QueryParam(QUERY_ACCOUNT_WITH_BALANCE_AND_CBA) @DefaultValue("false") final Boolean accountWithBalanceAndCBA,
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException {
+ final TenantContext tenantContext = context.createContext(request);
+ final Pagination<Account> accounts = accountUserApi.getAccounts(offset, limit, tenantContext);
+ final URI nextPageUri = uriBuilder.nextPage(AccountResource.class, "getAccounts", accounts.getNextOffset(), limit, ImmutableMap.<String, String>of());
+ return buildStreamingAccountsResponse(accounts, accountWithBalance, accountWithBalanceAndCBA, nextPageUri, tenantContext);
+ }
+
+ @GET
@Path("/" + SEARCH + "/{searchKey:" + ANYTHING_PATTERN + "}")
@Produces(APPLICATION_JSON)
public Response searchAccounts(@PathParam("searchKey") final String searchKey,
+ @QueryParam(QUERY_SEARCH_OFFSET) @DefaultValue("0") final Long offset,
+ @QueryParam(QUERY_SEARCH_LIMIT) @DefaultValue("100") final Long limit,
@QueryParam(QUERY_ACCOUNT_WITH_BALANCE) @DefaultValue("false") final Boolean accountWithBalance,
@QueryParam(QUERY_ACCOUNT_WITH_BALANCE_AND_CBA) @DefaultValue("false") final Boolean accountWithBalanceAndCBA,
@javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException {
final TenantContext tenantContext = context.createContext(request);
- final List<Account> accounts = accountUserApi.searchAccounts(searchKey, tenantContext);
- final List<AccountJson> accountsJson = ImmutableList.<AccountJson>copyOf(Collections2.transform(accounts, new Function<Account, AccountJson>() {
+ final Pagination<Account> accounts = accountUserApi.searchAccounts(searchKey, offset, limit, tenantContext);
+ final URI nextPageUri = uriBuilder.nextPage(AccountResource.class, "searchAccounts", accounts.getNextOffset(), limit, ImmutableMap.<String, String>of("searchKey", searchKey));
+ return buildStreamingAccountsResponse(accounts, accountWithBalance, accountWithBalanceAndCBA, nextPageUri, tenantContext);
+ }
+
+ private Response buildStreamingAccountsResponse(final Pagination<Account> accounts, final Boolean accountWithBalance,
+ final Boolean accountWithBalanceAndCBA, final URI nextPageUri, final TenantContext tenantContext) {
+ final StreamingOutput json = new StreamingOutput() {
@Override
- public AccountJson apply(final Account account) {
- return getAccount(account, accountWithBalance, accountWithBalanceAndCBA, tenantContext);
+ public void write(final OutputStream output) throws IOException, WebApplicationException {
+ final JsonGenerator generator = mapper.getFactory().createJsonGenerator(output);
+ generator.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false);
+
+ generator.writeStartArray();
+ for (final Account account : accounts) {
+ final AccountJson asJson = getAccount(account, accountWithBalance, accountWithBalanceAndCBA, tenantContext);
+ generator.writeObject(asJson);
+ }
+ generator.writeEndArray();
+ generator.close();
}
- }));
- return Response.status(Status.OK).entity(accountsJson).build();
+ };
+ return Response.status(Status.OK)
+ .entity(json)
+ .header(HDR_PAGINATION_CURRENT_OFFSET, accounts.getCurrentOffset())
+ .header(HDR_PAGINATION_NEXT_OFFSET, accounts.getNextOffset())
+ .header(HDR_PAGINATION_TOTAL_NB_RECORDS, accounts.getTotalNbRecords())
+ .header(HDR_PAGINATION_MAX_NB_RECORDS, accounts.getMaxNbRecords())
+ .header(HDR_PAGINATION_NEXT_PAGE_URI, nextPageUri)
+ .build();
}
-
@GET
@Path("/{accountId:" + UUID_PATTERN + "}/" + BUNDLES)
@Produces(APPLICATION_JSON)
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 8f91ef2..4d73b69 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
@@ -40,6 +40,11 @@ public interface JaxrsResource {
public static String HDR_CREATED_BY = "X-Killbill-CreatedBy";
public static String HDR_REASON = "X-Killbill-Reason";
public static String HDR_COMMENT = "X-Killbill-Comment";
+ public static String HDR_PAGINATION_CURRENT_OFFSET = "X-Killbill-Pagination-CurrentOffset";
+ public static String HDR_PAGINATION_NEXT_OFFSET = "X-Killbill-Pagination-NextOffset";
+ public static String HDR_PAGINATION_TOTAL_NB_RECORDS = "X-Killbill-Pagination-TotalNbRecords";
+ public static String HDR_PAGINATION_MAX_NB_RECORDS = "X-Killbill-Pagination-MaxNbRecords";
+ public static String HDR_PAGINATION_NEXT_PAGE_URI = "X-Killbill-Pagination-NextPageUri";
/*
* Patterns
@@ -62,6 +67,8 @@ public interface JaxrsResource {
public static final String QUERY_TARGET_DATE = "targetDate";
public static final String QUERY_BILLING_POLICY = "billingPolicy";
public static final String QUERY_ENTITLEMENT_POLICY = "entitlementPolicy";
+ public static final String QUERY_SEARCH_OFFSET = "offset";
+ public static final String QUERY_SEARCH_LIMIT = "limit";
public static final String QUERY_ACCOUNT_WITH_BALANCE = "accountWithBalance";
public static final String QUERY_ACCOUNT_WITH_BALANCE_AND_CBA = "accountWithBalanceAndCBA";
@@ -93,6 +100,8 @@ public interface JaxrsResource {
public static final String QUERY_NOTIFICATION_CALLBACK = "cb";
+ public static final String PAGINATION = "pagination";
+
public static final String ACCOUNTS = "accounts";
public static final String ACCOUNTS_PATH = PREFIX + "/" + ACCOUNTS;
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxRsResourceBase.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxRsResourceBase.java
index 3915e39..c24c399 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxRsResourceBase.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxRsResourceBase.java
@@ -53,6 +53,7 @@ import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.TenantContext;
import com.ning.billing.util.customfield.CustomField;
import com.ning.billing.util.customfield.StringCustomField;
+import com.ning.billing.util.jackson.ObjectMapper;
import com.ning.billing.util.tag.Tag;
import com.ning.billing.util.tag.TagDefinition;
@@ -64,6 +65,8 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
private static final Logger log = LoggerFactory.getLogger(JaxRsResourceBase.class);
+ protected static final ObjectMapper mapper = new ObjectMapper();
+
protected final JaxrsUriBuilder uriBuilder;
protected final TagUserApi tagUserApi;
protected final CustomFieldUserApi customFieldUserApi;
@@ -172,7 +175,7 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
protected LocalDate toLocalDate(final UUID accountId, final String inputDate, final TenantContext context) {
- final LocalDate maybeResult = extractLocalDate(inputDate);
+ final LocalDate maybeResult = extractLocalDate(inputDate);
if (maybeResult != null) {
return maybeResult;
}
@@ -187,10 +190,9 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
}
-
protected LocalDate toLocalDate(final Account account, final String inputDate, final TenantContext context) {
- final LocalDate maybeResult = extractLocalDate(inputDate);
+ final LocalDate maybeResult = extractLocalDate(inputDate);
if (maybeResult != null) {
return maybeResult;
}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentMethodResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentMethodResource.java
index 1218a08..1cdc630 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentMethodResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentMethodResource.java
@@ -16,8 +16,10 @@
package com.ning.billing.jaxrs.resources;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URI;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -30,8 +32,10 @@ import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
+import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.StreamingOutput;
import com.ning.billing.ObjectType;
import com.ning.billing.account.api.Account;
@@ -49,10 +53,11 @@ import com.ning.billing.util.api.CustomFieldUserApi;
import com.ning.billing.util.api.TagUserApi;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.TenantContext;
+import com.ning.billing.util.entity.Pagination;
-import com.google.common.base.Function;
+import com.fasterxml.jackson.core.JsonGenerator;
import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
+import com.google.common.collect.ImmutableMap;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -93,37 +98,85 @@ public class PaymentMethodResource extends JaxRsResourceBase {
}
@GET
+ @Path("/" + PAGINATION)
+ @Produces(APPLICATION_JSON)
+ public Response getPaymentMethods(@QueryParam(QUERY_SEARCH_OFFSET) @DefaultValue("0") final Long offset,
+ @QueryParam(QUERY_SEARCH_LIMIT) @DefaultValue("100") final Long limit,
+ @QueryParam(QUERY_PAYMENT_METHOD_PLUGIN_NAME) final String pluginName,
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException {
+ final TenantContext tenantContext = context.createContext(request);
+
+ final Pagination<PaymentMethod> paymentMethods;
+ final Map<String, String> nextUriParams = new HashMap<String, String>();
+ if (Strings.isNullOrEmpty(pluginName)) {
+ paymentMethods = paymentApi.getPaymentMethods(offset, limit, tenantContext);
+ } else {
+ paymentMethods = paymentApi.getPaymentMethods(offset, limit, pluginName, tenantContext);
+ nextUriParams.put(QUERY_PAYMENT_METHOD_PLUGIN_NAME, pluginName);
+ }
+
+ final URI nextPageUri = uriBuilder.nextPage(PaymentMethodResource.class, "getPaymentMethods", paymentMethods.getNextOffset(), limit, nextUriParams);
+ return buildStreamingPaymentMethodsResponse(paymentMethods, nextPageUri, tenantContext);
+ }
+
+ @GET
@Path("/" + SEARCH + "/{searchKey:" + ANYTHING_PATTERN + "}")
@Produces(APPLICATION_JSON)
public Response searchPaymentMethods(@PathParam("searchKey") final String searchKey,
+ @QueryParam(QUERY_SEARCH_OFFSET) @DefaultValue("0") final Long offset,
+ @QueryParam(QUERY_SEARCH_LIMIT) @DefaultValue("100") final Long limit,
@QueryParam(QUERY_PAYMENT_METHOD_PLUGIN_NAME) final String pluginName,
@javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
final TenantContext tenantContext = context.createContext(request);
// Search the plugin(s)
- final List<PaymentMethod> paymentMethods;
+ final Pagination<PaymentMethod> paymentMethods;
if (Strings.isNullOrEmpty(pluginName)) {
- paymentMethods = paymentApi.searchPaymentMethods(searchKey, tenantContext);
+ paymentMethods = paymentApi.searchPaymentMethods(searchKey, offset, limit, tenantContext);
} else {
- paymentMethods = paymentApi.searchPaymentMethods(searchKey, pluginName, tenantContext);
+ paymentMethods = paymentApi.searchPaymentMethods(searchKey, offset, limit, pluginName, tenantContext);
}
- // Lookup the associated account(s)
- final Map<UUID, Account> accounts = new HashMap<UUID, Account>();
- for (final PaymentMethod paymentMethod : paymentMethods) {
- if (accounts.get(paymentMethod.getAccountId()) == null) {
- final Account account = accountUserApi.getAccountById(paymentMethod.getAccountId(), tenantContext);
- accounts.put(paymentMethod.getAccountId(), account);
- }
- }
+ final URI nextPageUri = uriBuilder.nextPage(AccountResource.class, "searchPaymentMethods", paymentMethods.getNextOffset(), limit, ImmutableMap.<String, String>of());
+ return buildStreamingPaymentMethodsResponse(paymentMethods, nextPageUri, tenantContext);
+ }
- final List<PaymentMethodJson> json = Lists.transform(paymentMethods, new Function<PaymentMethod, PaymentMethodJson>() {
+ private Response buildStreamingPaymentMethodsResponse(final Pagination<PaymentMethod> paymentMethods, final URI nextPageUri, final TenantContext tenantContext) {
+ final Map<UUID, Account> accounts = new HashMap<UUID, Account>();
+ final StreamingOutput json = new StreamingOutput() {
@Override
- public PaymentMethodJson apply(final PaymentMethod paymentMethod) {
- return PaymentMethodJson.toPaymentMethodJson(accounts.get(paymentMethod.getAccountId()), paymentMethod);
+ public void write(final OutputStream output) throws IOException, WebApplicationException {
+ final JsonGenerator generator = mapper.getFactory().createJsonGenerator(output);
+ generator.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false);
+
+ generator.writeStartArray();
+ for (final PaymentMethod paymentMethod : paymentMethods) {
+ // Lookup the associated account(s)
+ if (accounts.get(paymentMethod.getAccountId()) == null) {
+ final Account account;
+ try {
+ account = accountUserApi.getAccountById(paymentMethod.getAccountId(), tenantContext);
+ accounts.put(paymentMethod.getAccountId(), account);
+ } catch (AccountApiException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ final PaymentMethodJson asJson = PaymentMethodJson.toPaymentMethodJson(accounts.get(paymentMethod.getAccountId()), paymentMethod);
+ generator.writeObject(asJson);
+ }
+ generator.writeEndArray();
+ generator.close();
}
- });
- return Response.status(Status.OK).entity(json).build();
+ };
+ return Response.status(Status.OK)
+ .entity(json)
+ .header(HDR_PAGINATION_CURRENT_OFFSET, paymentMethods.getCurrentOffset())
+ .header(HDR_PAGINATION_NEXT_OFFSET, paymentMethods.getNextOffset())
+ .header(HDR_PAGINATION_TOTAL_NB_RECORDS, paymentMethods.getTotalNbRecords())
+ .header(HDR_PAGINATION_MAX_NB_RECORDS, paymentMethods.getMaxNbRecords())
+ .header(HDR_PAGINATION_NEXT_PAGE_URI, nextPageUri)
+ .build();
}
@DELETE
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/util/JaxrsUriBuilder.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/util/JaxrsUriBuilder.java
index d7ca875..4a25ec0 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/util/JaxrsUriBuilder.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/util/JaxrsUriBuilder.java
@@ -13,12 +13,16 @@
* License for the specific language governing permissions and limitations
* under the License.
*/
+
package com.ning.billing.jaxrs.util;
+import java.net.URI;
+import java.util.Map;
+
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
-import java.net.URI;
+import com.ning.billing.jaxrs.resources.JaxRsResourceBase;
import com.ning.billing.jaxrs.resources.JaxrsResource;
public class JaxrsUriBuilder {
@@ -37,6 +41,21 @@ public class JaxrsUriBuilder {
}).build();
}
+ public URI nextPage(final Class<? extends JaxrsResource> theClass, final String getMethodName, final Long nextOffset, final Long limit, final Map<String, String> params) {
+ if (nextOffset == null || limit == null) {
+ // End of pagination?
+ return null;
+ }
+
+ final UriBuilder uriBuilder = UriBuilder.fromResource(theClass)
+ .path(theClass, getMethodName)
+ .queryParam(JaxRsResourceBase.QUERY_SEARCH_OFFSET, nextOffset)
+ .queryParam(JaxRsResourceBase.QUERY_SEARCH_LIMIT, limit);
+ for (final String key : params.keySet()) {
+ uriBuilder.queryParam(key, params.get(key));
+ }
+ return uriBuilder.build();
+ }
public Response buildResponse(final Class<? extends JaxrsResource> theClass, final String getMethodName, final Object objectId, final String baseUri) {
diff --git a/osgi/src/main/java/com/ning/billing/osgi/FileInstall.java b/osgi/src/main/java/com/ning/billing/osgi/FileInstall.java
index c318727..88066fa 100644
--- a/osgi/src/main/java/com/ning/billing/osgi/FileInstall.java
+++ b/osgi/src/main/java/com/ning/billing/osgi/FileInstall.java
@@ -187,6 +187,7 @@ public class FileInstall {
if (new File(expectedPath).isFile()) {
return expectedPath;
} else {
+ logger.warn("Unable to find the JRuby bundle at {}, ruby plugins won't be started!", expectedPath);
return null;
}
}
diff --git a/osgi-bundles/bundles/jruby/src/main/java/com/ning/billing/osgi/bundles/jruby/JRubyPaymentPlugin.java b/osgi-bundles/bundles/jruby/src/main/java/com/ning/billing/osgi/bundles/jruby/JRubyPaymentPlugin.java
index 4868c1f..f3ab64c 100644
--- a/osgi-bundles/bundles/jruby/src/main/java/com/ning/billing/osgi/bundles/jruby/JRubyPaymentPlugin.java
+++ b/osgi-bundles/bundles/jruby/src/main/java/com/ning/billing/osgi/bundles/jruby/JRubyPaymentPlugin.java
@@ -38,6 +38,7 @@ import com.ning.billing.payment.plugin.api.PaymentPluginApiException;
import com.ning.billing.payment.plugin.api.RefundInfoPlugin;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.TenantContext;
+import com.ning.billing.util.entity.Pagination;
public class JRubyPaymentPlugin extends JRubyPlugin implements PaymentPluginApi {
@@ -168,11 +169,11 @@ public class JRubyPaymentPlugin extends JRubyPlugin implements PaymentPluginApi
}
@Override
- public List<PaymentMethodPlugin> searchPaymentMethods(final String searchKey, final TenantContext tenantContext) throws PaymentPluginApiException {
+ public Pagination<PaymentMethodPlugin> searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final TenantContext tenantContext) throws PaymentPluginApiException {
return callWithRuntimeAndChecking(new PluginCallback(VALIDATION_PLUGIN_TYPE.PAYMENT) {
@Override
- public List<PaymentMethodPlugin> doCall(final Ruby runtime) throws PaymentPluginApiException {
- return ((PaymentPluginApi) pluginInstance).searchPaymentMethods(searchKey, tenantContext);
+ public Pagination<PaymentMethodPlugin> doCall(final Ruby runtime) throws PaymentPluginApiException {
+ return ((PaymentPluginApi) pluginInstance).searchPaymentMethods(searchKey, offset, limit, tenantContext);
}
});
}
diff --git a/osgi-bundles/tests/beatrix/src/test/java/com/ning/billing/osgi/bundles/test/TestPaymentPluginApi.java b/osgi-bundles/tests/beatrix/src/test/java/com/ning/billing/osgi/bundles/test/TestPaymentPluginApi.java
index 600f5af..c4ef01b 100644
--- a/osgi-bundles/tests/beatrix/src/test/java/com/ning/billing/osgi/bundles/test/TestPaymentPluginApi.java
+++ b/osgi-bundles/tests/beatrix/src/test/java/com/ning/billing/osgi/bundles/test/TestPaymentPluginApi.java
@@ -18,6 +18,7 @@ package com.ning.billing.osgi.bundles.test;
import java.math.BigDecimal;
import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
import java.util.UUID;
@@ -34,6 +35,7 @@ import com.ning.billing.payment.plugin.api.PaymentPluginStatus;
import com.ning.billing.payment.plugin.api.RefundInfoPlugin;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.TenantContext;
+import com.ning.billing.util.entity.Pagination;
public class TestPaymentPluginApi implements PaymentPluginApi {
@@ -129,8 +131,33 @@ public class TestPaymentPluginApi implements PaymentPluginApi {
}
@Override
- public List<PaymentMethodPlugin> searchPaymentMethods(final String searchKey, final TenantContext tenantContext) throws PaymentPluginApiException {
- return Collections.emptyList();
+ public Pagination<PaymentMethodPlugin> searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final TenantContext tenantContext) throws PaymentPluginApiException {
+ return new Pagination<PaymentMethodPlugin>() {
+ @Override
+ public Long getCurrentOffset() {
+ return 0L;
+ }
+
+ @Override
+ public Long getNextOffset() {
+ return null;
+ }
+
+ @Override
+ public Long getMaxNbRecords() {
+ return 0L;
+ }
+
+ @Override
+ public Long getTotalNbRecords() {
+ return 0L;
+ }
+
+ @Override
+ public Iterator<PaymentMethodPlugin> iterator() {
+ return null;
+ }
+ };
}
@Override
diff --git a/osgi-bundles/tests/payment/src/test/java/com/ning/billing/osgi/bundles/test/TestPaymentPluginApi.java b/osgi-bundles/tests/payment/src/test/java/com/ning/billing/osgi/bundles/test/TestPaymentPluginApi.java
index 804e548..c9059b0 100644
--- a/osgi-bundles/tests/payment/src/test/java/com/ning/billing/osgi/bundles/test/TestPaymentPluginApi.java
+++ b/osgi-bundles/tests/payment/src/test/java/com/ning/billing/osgi/bundles/test/TestPaymentPluginApi.java
@@ -18,6 +18,7 @@ package com.ning.billing.osgi.bundles.test;
import java.math.BigDecimal;
import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
import java.util.UUID;
@@ -34,6 +35,7 @@ import com.ning.billing.payment.plugin.api.RefundInfoPlugin;
import com.ning.billing.payment.plugin.api.RefundPluginStatus;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.TenantContext;
+import com.ning.billing.util.entity.Pagination;
public class TestPaymentPluginApi implements PaymentPluginApiWithTestControl {
@@ -54,22 +56,27 @@ public class TestPaymentPluginApi implements PaymentPluginApiWithTestControl {
public BigDecimal getAmount() {
return amount;
}
+
@Override
public DateTime getCreatedDate() {
return new DateTime();
}
+
@Override
public DateTime getEffectiveDate() {
return new DateTime();
}
+
@Override
public PaymentPluginStatus getStatus() {
return PaymentPluginStatus.PROCESSED;
}
+
@Override
public String getGatewayError() {
return null;
}
+
@Override
public String getGatewayErrorCode() {
return null;
@@ -96,22 +103,27 @@ public class TestPaymentPluginApi implements PaymentPluginApiWithTestControl {
public BigDecimal getAmount() {
return someAmount;
}
+
@Override
public DateTime getCreatedDate() {
return new DateTime();
}
+
@Override
public DateTime getEffectiveDate() {
return new DateTime();
}
+
@Override
public PaymentPluginStatus getStatus() {
return PaymentPluginStatus.PROCESSED;
}
+
@Override
public String getGatewayError() {
return null;
}
+
@Override
public String getGatewayErrorCode() {
return null;
@@ -138,22 +150,27 @@ public class TestPaymentPluginApi implements PaymentPluginApiWithTestControl {
public BigDecimal getAmount() {
return null;
}
+
@Override
public DateTime getCreatedDate() {
return null;
}
+
@Override
public DateTime getEffectiveDate() {
return null;
}
+
@Override
public RefundPluginStatus getStatus() {
return null;
}
+
@Override
public String getGatewayError() {
return null;
}
+
@Override
public String getGatewayErrorCode() {
return null;
@@ -194,8 +211,33 @@ public class TestPaymentPluginApi implements PaymentPluginApiWithTestControl {
}
@Override
- public List<PaymentMethodPlugin> searchPaymentMethods(final String s, final TenantContext tenantContext) throws PaymentPluginApiException {
- return Collections.emptyList();
+ public Pagination<PaymentMethodPlugin> searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final TenantContext tenantContext) throws PaymentPluginApiException {
+ return new Pagination<PaymentMethodPlugin>() {
+ @Override
+ public Long getCurrentOffset() {
+ return 0L;
+ }
+
+ @Override
+ public Long getNextOffset() {
+ return null;
+ }
+
+ @Override
+ public Long getMaxNbRecords() {
+ return 0L;
+ }
+
+ @Override
+ public Long getTotalNbRecords() {
+ return 0L;
+ }
+
+ @Override
+ public Iterator<PaymentMethodPlugin> iterator() {
+ return null;
+ }
+ };
}
@Override
diff --git a/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
index d0f7b65..579ded0 100644
--- a/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
+++ b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
@@ -26,13 +26,18 @@ import java.util.UUID;
import com.ning.billing.ErrorCode;
import com.ning.billing.account.api.Account;
+import com.ning.billing.callcontext.DefaultCallContext;
+import com.ning.billing.callcontext.InternalCallContext;
+import com.ning.billing.clock.Clock;
import com.ning.billing.payment.core.PaymentMethodProcessor;
import com.ning.billing.payment.core.PaymentProcessor;
import com.ning.billing.payment.core.RefundProcessor;
import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.callcontext.InternalCallContext;
+import com.ning.billing.util.callcontext.CallOrigin;
import com.ning.billing.util.callcontext.InternalCallContextFactory;
import com.ning.billing.util.callcontext.TenantContext;
+import com.ning.billing.util.callcontext.UserType;
+import com.ning.billing.util.entity.Pagination;
import com.google.common.collect.ImmutableMap;
import com.google.inject.Inject;
@@ -43,15 +48,18 @@ public class DefaultPaymentApi implements PaymentApi {
private final PaymentProcessor paymentProcessor;
private final RefundProcessor refundProcessor;
private final InternalCallContextFactory internalCallContextFactory;
+ private final Clock clock;
@Inject
public DefaultPaymentApi(final PaymentMethodProcessor methodProcessor,
final PaymentProcessor paymentProcessor,
final RefundProcessor refundProcessor,
+ final Clock clock,
final InternalCallContextFactory internalCallContextFactory) {
this.methodProcessor = methodProcessor;
this.paymentProcessor = paymentProcessor;
this.refundProcessor = refundProcessor;
+ this.clock = clock;
this.internalCallContextFactory = internalCallContextFactory;
}
@@ -173,13 +181,27 @@ public class DefaultPaymentApi implements PaymentApi {
}
@Override
- public List<PaymentMethod> searchPaymentMethods(final String searchKey, final TenantContext context) {
- return methodProcessor.searchPaymentMethods(searchKey, internalCallContextFactory.createInternalTenantContext(context));
+ public Pagination<PaymentMethod> getPaymentMethods(final Long offset, final Long limit, final TenantContext context) {
+ // Lame, but required
+ final CallContext callContext = new DefaultCallContext(context.getTenantId(), DefaultPaymentApi.class.toString(), CallOrigin.EXTERNAL, UserType.CUSTOMER, UUID.randomUUID(), clock);
+ return methodProcessor.getPaymentMethods(offset, limit, context, callContext, internalCallContextFactory.createInternalTenantContext(context));
+ }
+
+ @Override
+ public Pagination<PaymentMethod> getPaymentMethods(final Long offset, final Long limit, final String pluginName, final TenantContext context) throws PaymentApiException {
+ // Lame, but required
+ final CallContext callContext = new DefaultCallContext(context.getTenantId(), DefaultPaymentApi.class.toString(), CallOrigin.EXTERNAL, UserType.CUSTOMER, UUID.randomUUID(), clock);
+ return methodProcessor.getPaymentMethods(offset, limit, pluginName, context, callContext, internalCallContextFactory.createInternalTenantContext(context));
+ }
+
+ @Override
+ public Pagination<PaymentMethod> searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final TenantContext context) {
+ return methodProcessor.searchPaymentMethods(searchKey, offset, limit, internalCallContextFactory.createInternalTenantContext(context));
}
@Override
- public List<PaymentMethod> searchPaymentMethods(final String searchKey, final String pluginName, final TenantContext context) throws PaymentApiException {
- return methodProcessor.searchPaymentMethods(searchKey, pluginName, internalCallContextFactory.createInternalTenantContext(context));
+ public Pagination<PaymentMethod> searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final String pluginName, final TenantContext context) throws PaymentApiException {
+ return methodProcessor.searchPaymentMethods(searchKey, offset, limit, pluginName, internalCallContextFactory.createInternalTenantContext(context));
}
@Override
diff --git a/payment/src/main/java/com/ning/billing/payment/core/PaymentMethodProcessor.java b/payment/src/main/java/com/ning/billing/payment/core/PaymentMethodProcessor.java
index cd50517..f720133 100644
--- a/payment/src/main/java/com/ning/billing/payment/core/PaymentMethodProcessor.java
+++ b/payment/src/main/java/com/ning/billing/payment/core/PaymentMethodProcessor.java
@@ -32,8 +32,12 @@ import org.slf4j.LoggerFactory;
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.AccountInternalApi;
import com.ning.billing.bus.api.PersistentBus;
+import com.ning.billing.callcontext.InternalCallContext;
+import com.ning.billing.callcontext.InternalTenantContext;
import com.ning.billing.commons.locker.GlobalLocker;
+import com.ning.billing.invoice.api.InvoiceInternalApi;
import com.ning.billing.osgi.api.OSGIServiceRegistration;
import com.ning.billing.payment.api.DefaultPaymentMethod;
import com.ning.billing.payment.api.PaymentApiException;
@@ -48,16 +52,18 @@ import com.ning.billing.payment.plugin.api.PaymentPluginApiException;
import com.ning.billing.payment.provider.DefaultNoOpPaymentMethodPlugin;
import com.ning.billing.payment.provider.DefaultPaymentMethodInfoPlugin;
import com.ning.billing.payment.provider.ExternalPaymentProviderPlugin;
-import com.ning.billing.callcontext.InternalCallContext;
-import com.ning.billing.callcontext.InternalTenantContext;
-import com.ning.billing.util.dao.NonEntityDao;
-import com.ning.billing.account.api.AccountInternalApi;
-import com.ning.billing.invoice.api.InvoiceInternalApi;
import com.ning.billing.tag.TagInternalApi;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.TenantContext;
+import com.ning.billing.util.dao.NonEntityDao;
+import com.ning.billing.util.entity.DefaultPagination;
+import com.ning.billing.util.entity.Pagination;
import com.google.common.base.Function;
+import com.google.common.base.Predicates;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterators;
import com.google.inject.Inject;
import com.google.inject.name.Named;
@@ -153,49 +159,128 @@ public class PaymentMethodProcessor extends ProcessorBase {
return new DefaultPaymentMethod(paymentMethodModelDao, paymentMethodPlugin);
}
- public List<PaymentMethod> searchPaymentMethods(final String searchKey, final InternalTenantContext internalTenantContext) {
- final List<PaymentMethod> results = new LinkedList<PaymentMethod>();
+ public Pagination<PaymentMethod> getPaymentMethods(final Long offset, final Long limit, final TenantContext tenantContext, final CallContext callContext, final InternalTenantContext internalTenantContext) {
+ // Note that we cannot easily do streaming here, since we would have to rely on the statistics
+ // returned by the Pagination objects from the plugins and we probably don't want to do that (if
+ // one plugin gets it wrong, it may starve the others).
+ final List<PaymentMethod> allResults = new LinkedList<PaymentMethod>();
- // Search in all plugins
+ // Search in all plugins (we treat the full set of results as a union with respect to offset/limit)
for (final String pluginName : getAvailablePlugins()) {
try {
- results.addAll(searchPaymentMethods(searchKey, pluginName, internalTenantContext));
+ final Pagination<PaymentMethod> paymentMethods = getPaymentMethods(0L, Long.MAX_VALUE, pluginName, tenantContext, callContext, internalTenantContext);
+ allResults.addAll(ImmutableList.<PaymentMethod>copyOf(paymentMethods));
+ if (allResults.size() > offset + limit) {
+ break;
+ }
} catch (PaymentApiException e) {
log.warn("Error while searching plugin " + pluginName, e);
// Non-fatal, continue to search other plugins
}
}
- return results;
+ return DefaultPagination.<PaymentMethod>build(offset, limit, allResults);
}
- public List<PaymentMethod> searchPaymentMethods(final String searchKey, final String pluginName, final InternalTenantContext internalTenantContext) throws PaymentApiException {
+ public Pagination<PaymentMethod> getPaymentMethods(final Long offset, final Long limit, final String pluginName, final TenantContext tenantContext, final CallContext callContext, final InternalTenantContext internalTenantContext) throws PaymentApiException {
final PaymentPluginApi pluginApi = getPaymentPluginApi(pluginName);
- final List<PaymentMethodPlugin> paymentMethods;
- try {
- paymentMethods = pluginApi.searchPaymentMethods(searchKey, buildTenantContext(internalTenantContext));
- } catch (PaymentPluginApiException e) {
- throw new PaymentApiException(e, ErrorCode.PAYMENT_PLUGIN_SEARCH_PAYMENT_METHODS, pluginName, searchKey);
- }
+ final List<PaymentMethodPlugin> paymentMethods = new LinkedList<PaymentMethodPlugin>();
- final List<PaymentMethod> results = new LinkedList<PaymentMethod>();
- for (final PaymentMethodPlugin paymentMethodPlugin : paymentMethods) {
- if (paymentMethodPlugin.getKbPaymentMethodId() == null) {
- // Garbage from the plugin?
- log.debug("Plugin {} returned a payment method without a kbPaymentMethodId for searchKey {}", pluginName, searchKey);
- continue;
+ // Find all payment methods for all accounts
+ final Pagination<Account> accounts = accountInternalApi.getAccounts(0L, Long.MAX_VALUE, internalTenantContext);
+ for (final Account account : accounts) {
+ try {
+ final List<PaymentMethodInfoPlugin> paymentMethodInfos = pluginApi.getPaymentMethods(account.getId(), true, callContext);
+ for (final PaymentMethodInfoPlugin paymentMethodInfoPlugin : paymentMethodInfos) {
+ final PaymentMethodPlugin paymentMethodDetail = pluginApi.getPaymentMethodDetail(account.getId(), paymentMethodInfoPlugin.getPaymentMethodId(), tenantContext);
+ paymentMethods.add(paymentMethodDetail);
+ if (paymentMethods.size() > offset + limit) {
+ break;
+ }
+ }
+ } catch (PaymentPluginApiException e) {
+ throw new PaymentApiException(e, ErrorCode.PAYMENT_PLUGIN_GET_PAYMENT_METHODS, pluginName);
}
+ }
+
+ return new DefaultPagination<PaymentMethod>(DefaultPagination.<PaymentMethodPlugin>build(offset, limit, paymentMethods),
+ limit,
+ Iterators.<PaymentMethod>filter(Iterators.<PaymentMethodPlugin, PaymentMethod>transform(paymentMethods.iterator(),
+ new Function<PaymentMethodPlugin, PaymentMethod>() {
+ @Override
+ public PaymentMethod apply(final PaymentMethodPlugin paymentMethodPlugin) {
+ if (paymentMethodPlugin.getKbPaymentMethodId() == null) {
+ // Garbage from the plugin?
+ log.debug("Plugin {} returned a payment method without a kbPaymentMethodId", pluginName);
+ return null;
+ }
+
+ final PaymentMethodModelDao paymentMethodModelDao = paymentDao.getPaymentMethodIncludedDeleted(paymentMethodPlugin.getKbPaymentMethodId(), internalTenantContext);
+ if (paymentMethodModelDao == null) {
+ log.warn("Unable to find payment method id " + paymentMethodPlugin.getKbPaymentMethodId() + " present in plugin " + pluginName);
+ return null;
+ }
+
+ return new DefaultPaymentMethod(paymentMethodModelDao, paymentMethodPlugin);
+ }
+ }),
+ Predicates.<PaymentMethod>notNull()));
+ }
+
+ public Pagination<PaymentMethod> searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final InternalTenantContext internalTenantContext) {
+ // Note that we cannot easily do streaming here, since we would have to rely on the statistics
+ // returned by the Pagination objects from the plugins and we probably don't want to do that (if
+ // one plugin gets it wrong, it may starve the others).
+ final List<PaymentMethod> allResults = new LinkedList<PaymentMethod>();
- final PaymentMethodModelDao paymentMethodModelDao = paymentDao.getPaymentMethodIncludedDeleted(paymentMethodPlugin.getKbPaymentMethodId(), internalTenantContext);
- if (paymentMethodModelDao == null) {
- log.warn("Unable to find payment method id " + paymentMethodPlugin.getKbPaymentMethodId() + " present in plugin " + pluginName);
- continue;
+ // Search in all plugins (we treat the full set of results as a union with respect to offset/limit)
+ for (final String pluginName : getAvailablePlugins()) {
+ try {
+ final Pagination<PaymentMethod> paymentMethods = searchPaymentMethods(searchKey, 0L, Long.MAX_VALUE, pluginName, internalTenantContext);
+ allResults.addAll(ImmutableList.<PaymentMethod>copyOf(paymentMethods));
+ if (allResults.size() > offset + limit) {
+ break;
+ }
+ } catch (PaymentApiException e) {
+ log.warn("Error while searching plugin " + pluginName, e);
+ // Non-fatal, continue to search other plugins
}
+ }
+
+ return DefaultPagination.<PaymentMethod>build(offset, limit, allResults);
+ }
- results.add(new DefaultPaymentMethod(paymentMethodModelDao, paymentMethodPlugin));
+ public Pagination<PaymentMethod> searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final String pluginName, final InternalTenantContext internalTenantContext) throws PaymentApiException {
+ final PaymentPluginApi pluginApi = getPaymentPluginApi(pluginName);
+ final Pagination<PaymentMethodPlugin> paymentMethods;
+ try {
+ paymentMethods = pluginApi.searchPaymentMethods(searchKey, offset, limit, buildTenantContext(internalTenantContext));
+ } catch (PaymentPluginApiException e) {
+ throw new PaymentApiException(e, ErrorCode.PAYMENT_PLUGIN_SEARCH_PAYMENT_METHODS, pluginName, searchKey);
}
- return results;
+ return new DefaultPagination<PaymentMethod>(paymentMethods,
+ limit,
+ Iterators.<PaymentMethod>filter(Iterators.<PaymentMethodPlugin, PaymentMethod>transform(paymentMethods.iterator(),
+ new Function<PaymentMethodPlugin, PaymentMethod>() {
+ @Override
+ public PaymentMethod apply(final PaymentMethodPlugin paymentMethodPlugin) {
+ if (paymentMethodPlugin.getKbPaymentMethodId() == null) {
+ // Garbage from the plugin?
+ log.debug("Plugin {} returned a payment method without a kbPaymentMethodId for searchKey {}", pluginName, searchKey);
+ return null;
+ }
+
+ final PaymentMethodModelDao paymentMethodModelDao = paymentDao.getPaymentMethodIncludedDeleted(paymentMethodPlugin.getKbPaymentMethodId(), internalTenantContext);
+ if (paymentMethodModelDao == null) {
+ log.warn("Unable to find payment method id " + paymentMethodPlugin.getKbPaymentMethodId() + " present in plugin " + pluginName);
+ return null;
+ }
+
+ return new DefaultPaymentMethod(paymentMethodModelDao, paymentMethodPlugin);
+ }
+ }),
+ Predicates.<PaymentMethod>notNull()));
}
public PaymentMethod getExternalPaymentMethod(final Account account, final InternalTenantContext context) throws PaymentApiException {
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java b/payment/src/main/java/com/ning/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java
index 449324a..f934d59 100644
--- a/payment/src/main/java/com/ning/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java
+++ b/payment/src/main/java/com/ning/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java
@@ -25,6 +25,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.clock.Clock;
import com.ning.billing.payment.api.PaymentMethodPlugin;
import com.ning.billing.payment.plugin.api.NoOpPaymentPluginApi;
import com.ning.billing.payment.plugin.api.PaymentInfoPlugin;
@@ -35,7 +36,8 @@ import com.ning.billing.payment.plugin.api.RefundInfoPlugin;
import com.ning.billing.payment.plugin.api.RefundPluginStatus;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.TenantContext;
-import com.ning.billing.clock.Clock;
+import com.ning.billing.util.entity.DefaultPagination;
+import com.ning.billing.util.entity.Pagination;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
@@ -157,8 +159,8 @@ public class DefaultNoOpPaymentProviderPlugin implements NoOpPaymentPluginApi {
}
@Override
- public List<PaymentMethodPlugin> searchPaymentMethods(final String searchKey, final TenantContext tenantContext) throws PaymentPluginApiException {
- return ImmutableList.<PaymentMethodPlugin>copyOf(Iterables.<PaymentMethodPlugin>filter(Iterables.<PaymentMethodPlugin>concat(paymentMethods.values()), new Predicate<PaymentMethodPlugin>() {
+ public Pagination<PaymentMethodPlugin> searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final TenantContext tenantContext) throws PaymentPluginApiException {
+ final ImmutableList<PaymentMethodPlugin> results = ImmutableList.<PaymentMethodPlugin>copyOf(Iterables.<PaymentMethodPlugin>filter(Iterables.<PaymentMethodPlugin>concat(paymentMethods.values()), new Predicate<PaymentMethodPlugin>() {
@Override
public boolean apply(final PaymentMethodPlugin input) {
return (input.getAddress1() != null && input.getAddress1().contains(searchKey)) ||
@@ -170,6 +172,7 @@ public class DefaultNoOpPaymentProviderPlugin implements NoOpPaymentPluginApi {
(input.getCountry() != null && input.getCountry().contains(searchKey));
}
}));
+ return DefaultPagination.<PaymentMethodPlugin>build(offset, limit, results);
}
@Override
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/ExternalPaymentProviderPlugin.java b/payment/src/main/java/com/ning/billing/payment/provider/ExternalPaymentProviderPlugin.java
index b1b6915..1605150 100644
--- a/payment/src/main/java/com/ning/billing/payment/provider/ExternalPaymentProviderPlugin.java
+++ b/payment/src/main/java/com/ning/billing/payment/provider/ExternalPaymentProviderPlugin.java
@@ -34,8 +34,11 @@ import com.ning.billing.payment.plugin.api.RefundInfoPlugin;
import com.ning.billing.payment.plugin.api.RefundPluginStatus;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.TenantContext;
+import com.ning.billing.util.entity.DefaultPagination;
+import com.ning.billing.util.entity.Pagination;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterators;
import com.google.inject.Inject;
/**
@@ -98,8 +101,8 @@ public class ExternalPaymentProviderPlugin implements PaymentPluginApi {
}
@Override
- public List<PaymentMethodPlugin> searchPaymentMethods(final String s, final TenantContext tenantContext) throws PaymentPluginApiException {
- return ImmutableList.<PaymentMethodPlugin>of();
+ public Pagination<PaymentMethodPlugin> searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final TenantContext tenantContext) throws PaymentPluginApiException {
+ return new DefaultPagination<PaymentMethodPlugin>(offset, limit, 0L, 0L, Iterators.<PaymentMethodPlugin>emptyIterator());
}
@Override
diff --git a/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java
index 3768b18..0d383c6 100644
--- a/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java
+++ b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java
@@ -37,6 +37,8 @@ import com.ning.billing.payment.plugin.api.RefundInfoPlugin;
import com.ning.billing.payment.plugin.api.RefundPluginStatus;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.TenantContext;
+import com.ning.billing.util.entity.DefaultPagination;
+import com.ning.billing.util.entity.Pagination;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
@@ -146,8 +148,8 @@ public class MockPaymentProviderPlugin implements NoOpPaymentPluginApi {
}
@Override
- public List<PaymentMethodPlugin> searchPaymentMethods(final String searchKey, final TenantContext tenantContext) throws PaymentPluginApiException {
- return ImmutableList.<PaymentMethodPlugin>copyOf(Iterables.<PaymentMethodPlugin>filter(paymentMethods.values(), new Predicate<PaymentMethodPlugin>() {
+ public Pagination<PaymentMethodPlugin> searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final TenantContext tenantContext) throws PaymentPluginApiException {
+ final ImmutableList<PaymentMethodPlugin> results = ImmutableList.<PaymentMethodPlugin>copyOf(Iterables.<PaymentMethodPlugin>filter(paymentMethods.values(), new Predicate<PaymentMethodPlugin>() {
@Override
public boolean apply(final PaymentMethodPlugin input) {
return (input.getAddress1() != null && input.getAddress1().contains(searchKey)) ||
@@ -159,6 +161,7 @@ public class MockPaymentProviderPlugin implements NoOpPaymentPluginApi {
(input.getCountry() != null && input.getCountry().contains(searchKey));
}
}));
+ return DefaultPagination.<PaymentMethodPlugin>build(offset, limit, results);
}
@Override
pom.xml 2(+1 -1)
diff --git a/pom.xml b/pom.xml
index 274a9b5..1f51d19 100644
--- a/pom.xml
+++ b/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill-oss-parent</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.4.18</version>
+ <version>0.5.1</version>
</parent>
<artifactId>killbill</artifactId>
<version>0.6.18-SNAPSHOT</version>
diff --git a/util/src/main/java/com/ning/billing/util/entity/dao/EntityDao.java b/util/src/main/java/com/ning/billing/util/entity/dao/EntityDao.java
index 9e09c6c..8989b1b 100644
--- a/util/src/main/java/com/ning/billing/util/entity/dao/EntityDao.java
+++ b/util/src/main/java/com/ning/billing/util/entity/dao/EntityDao.java
@@ -16,13 +16,13 @@
package com.ning.billing.util.entity.dao;
-import java.util.List;
import java.util.UUID;
import com.ning.billing.BillingExceptionBase;
import com.ning.billing.callcontext.InternalCallContext;
import com.ning.billing.callcontext.InternalTenantContext;
import com.ning.billing.util.entity.Entity;
+import com.ning.billing.util.entity.Pagination;
public interface EntityDao<M extends EntityModelDao<E>, E extends Entity, U extends BillingExceptionBase> {
@@ -34,7 +34,11 @@ public interface EntityDao<M extends EntityModelDao<E>, E extends Entity, U exte
public M getById(UUID id, InternalTenantContext context);
- public List<M> get(InternalTenantContext context);
+ public Pagination<M> getAll(InternalTenantContext context);
+
+ public Pagination<M> get(Long offset, Long limit, InternalTenantContext context);
+
+ public Long getCount(InternalTenantContext context);
public void test(InternalTenantContext context);
}
diff --git a/util/src/main/java/com/ning/billing/util/entity/dao/EntityDaoBase.java b/util/src/main/java/com/ning/billing/util/entity/dao/EntityDaoBase.java
index 1c2704c..d494c4e 100644
--- a/util/src/main/java/com/ning/billing/util/entity/dao/EntityDaoBase.java
+++ b/util/src/main/java/com/ning/billing/util/entity/dao/EntityDaoBase.java
@@ -16,14 +16,16 @@
package com.ning.billing.util.entity.dao;
-import java.util.List;
+import java.util.Iterator;
import java.util.UUID;
import com.ning.billing.BillingExceptionBase;
-import com.ning.billing.util.audit.ChangeType;
import com.ning.billing.callcontext.InternalCallContext;
import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.util.audit.ChangeType;
+import com.ning.billing.util.entity.DefaultPagination;
import com.ning.billing.util.entity.Entity;
+import com.ning.billing.util.entity.Pagination;
public abstract class EntityDaoBase<M extends EntityModelDao<E>, E extends Entity, U extends BillingExceptionBase> implements EntityDao<M, E, U> {
@@ -71,6 +73,10 @@ public abstract class EntityDaoBase<M extends EntityModelDao<E>, E extends Entit
protected abstract U generateAlreadyExistsException(final M entity, final InternalCallContext context);
+ protected String getNaturalOrderingColumns() {
+ return "recordId";
+ }
+
@Override
public Long getRecordId(final UUID id, final InternalTenantContext context) {
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Long>() {
@@ -108,13 +114,56 @@ public abstract class EntityDaoBase<M extends EntityModelDao<E>, E extends Entit
}
@Override
- public List<M> get(final InternalTenantContext context) {
- return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<M>>() {
+ public Pagination<M> getAll(final InternalTenantContext context) {
+ // We usually always want to wrap our queries in an EntitySqlDaoTransactionWrapper... except here.
+ // Since we want to stream the results out, we don't want to auto-commit when this method returns.
+ final EntitySqlDao<M, E> sqlDao = transactionalSqlDao.onDemand(realSqlDao);
+
+ // Note: we need to perform the count before streaming the results, as the connection
+ // will be busy as we stream the results out. This is also why we cannot use
+ // SQL_CALC_FOUND_ROWS / FOUND_ROWS (which may ne be faster anyways).
+ final Long count = sqlDao.getCount(context);
+
+ final Iterator<M> results = sqlDao.getAll(context);
+ return new DefaultPagination<M>(count, results);
+ }
+ @Override
+ public Pagination<M> get(final Long offset, final Long limit, final InternalTenantContext context) {
+ // Note: the connection will be busy as we stream the results out: hence we cannot use
+ // SQL_CALC_FOUND_ROWS / FOUND_ROWS on the actual query.
+ // We still need to know the actual number of results, mainly for the UI so that it knows if it needs to fetch
+ // more pages. To do that, we perform a dummy search query with SQL_CALC_FOUND_ROWS (but limit 1).
+ final Long count = transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Long>() {
@Override
- public List<M> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+ public Long inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+ final EntitySqlDao<M, E> sqlDao = entitySqlDaoWrapperFactory.become(realSqlDao);
+ final Iterator<M> dumbIterator = sqlDao.get(offset, 1L, getNaturalOrderingColumns(), context);
+ // Make sure to go through the results to close the connection
+ while (dumbIterator.hasNext()) {
+ dumbIterator.next();
+ }
+ return sqlDao.getFoundRows(context);
+ }
+ });
+
+ // We usually always want to wrap our queries in an EntitySqlDaoTransactionWrapper... except here.
+ // Since we want to stream the results out, we don't want to auto-commit when this method returns.
+ final EntitySqlDao<M, E> sqlDao = transactionalSqlDao.onDemand(realSqlDao);
+ final Long totalCount = sqlDao.getCount(context);
+ final Iterator<M> results = sqlDao.get(offset, limit, getNaturalOrderingColumns(), context);
+
+ return new DefaultPagination<M>(offset, limit, count, totalCount, results);
+ }
+
+ @Override
+ public Long getCount(final InternalTenantContext context) {
+ return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Long>() {
+
+ @Override
+ public Long inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
final EntitySqlDao<M, E> transactional = entitySqlDaoWrapperFactory.become(realSqlDao);
- return transactional.get(context);
+ return transactional.getCount(context);
}
});
}
diff --git a/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDao.java b/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDao.java
index 662cca8..f4c9148 100644
--- a/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDao.java
@@ -16,26 +16,28 @@
package com.ning.billing.util.entity.dao;
+import java.util.Iterator;
import java.util.List;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.BindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.FetchSize;
import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import com.ning.billing.callcontext.InternalCallContext;
+import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.entity.EntityPersistenceException;
import com.ning.billing.util.audit.ChangeType;
import com.ning.billing.util.cache.Cachable;
import com.ning.billing.util.cache.Cachable.CacheType;
import com.ning.billing.util.cache.CachableKey;
-import com.ning.billing.callcontext.InternalCallContext;
-import com.ning.billing.callcontext.InternalTenantContext;
import com.ning.billing.util.dao.AuditSqlDao;
import com.ning.billing.util.dao.HistorySqlDao;
import com.ning.billing.util.entity.Entity;
-import com.ning.billing.entity.EntityPersistenceException;
// TODO get rid of Transmogrifier, but code does not compile even if we create the
// method public <T> T become(Class<T> typeToBecome); ?
@@ -66,10 +68,26 @@ public interface EntitySqlDao<M extends EntityModelDao<E>, E extends Entity> ext
@BindBean final InternalTenantContext context);
@SqlQuery
- public List<M> get(@BindBean final InternalTenantContext context);
+ public Long getFoundRows(@BindBean final InternalTenantContext context);
- @SqlUpdate
- public void test(@BindBean final InternalTenantContext context);
+ @SqlQuery
+ // Magic value to force MySQL to stream from the database
+ // See http://dev.mysql.com/doc/refman/5.0/en/connector-j-reference-implementation-notes.html (ResultSet)
+ @FetchSize(Integer.MIN_VALUE)
+ public Iterator<M> getAll(@BindBean final InternalTenantContext context);
+ @SqlQuery
+ // Magic value to force MySQL to stream from the database
+ // See http://dev.mysql.com/doc/refman/5.0/en/connector-j-reference-implementation-notes.html (ResultSet)
+ @FetchSize(Integer.MIN_VALUE)
+ public Iterator<M> get(@Bind("offset") final Long offset,
+ @Bind("rowCount") final Long rowCount,
+ @Bind("orderBy") final String orderBy,
+ @BindBean final InternalTenantContext context);
+ @SqlQuery
+ public Long getCount(@BindBean final InternalTenantContext context);
+
+ @SqlUpdate
+ public void test(@BindBean final InternalTenantContext context);
}
diff --git a/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDaoTransactionalJdbiWrapper.java b/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDaoTransactionalJdbiWrapper.java
index db23b1f..db6160f 100644
--- a/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDaoTransactionalJdbiWrapper.java
+++ b/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDaoTransactionalJdbiWrapper.java
@@ -21,8 +21,8 @@ import org.skife.jdbi.v2.Transaction;
import org.skife.jdbi.v2.TransactionIsolationLevel;
import org.skife.jdbi.v2.TransactionStatus;
-import com.ning.billing.util.cache.CacheControllerDispatcher;
import com.ning.billing.clock.Clock;
+import com.ning.billing.util.cache.CacheControllerDispatcher;
import com.ning.billing.util.dao.NonEntityDao;
import com.ning.billing.util.entity.Entity;
@@ -71,6 +71,10 @@ public class EntitySqlDaoTransactionalJdbiWrapper {
return entitySqlDao.inTransaction(TransactionIsolationLevel.READ_COMMITTED, new JdbiTransaction<ReturnType, EntityModelDao<Entity>, Entity>(entitySqlDaoTransactionWrapper));
}
+ public <M extends EntityModelDao<E>, E extends Entity, T extends EntitySqlDao<M, E>> T onDemand(final Class<T> sqlObjectType) {
+ return dbi.onDemand(sqlObjectType);
+ }
+
/**
* @param entitySqlDaoTransactionWrapper transaction to execute
* @param <ReturnType> object type to return from the transaction
diff --git a/util/src/main/java/com/ning/billing/util/entity/DefaultPagination.java b/util/src/main/java/com/ning/billing/util/entity/DefaultPagination.java
new file mode 100644
index 0000000..e766255
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/entity/DefaultPagination.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2010-2013 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.entity;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import com.google.common.collect.ImmutableList;
+
+// Assumes the original offset starts at zero.
+public class DefaultPagination<T> implements Pagination<T> {
+
+ private final Long currentOffset;
+ private final Long limit;
+ private final Long totalNbRecords;
+ private final Long maxNbRecords;
+ private final Iterator<T> delegateIterator;
+
+ // Builder when the streaming API can't be used
+ // Notes: elements should be the entire records set (regardless of filtering) otherwise maxNbRecords won't be accurate
+ public static <T> Pagination<T> build(final Long offset, final Long limit, final Collection<T> elements) {
+ final List<T> allResults = ImmutableList.<T>copyOf(elements);
+
+ final List<T> results;
+ if (offset >= allResults.size()) {
+ results = ImmutableList.<T>of();
+ } else if (offset + limit > allResults.size()) {
+ results = allResults.subList(offset.intValue(), allResults.size());
+ } else {
+ results = allResults.subList(offset.intValue(), offset.intValue() + limit.intValue());
+ }
+ return new DefaultPagination<T>(offset, limit, (long) results.size(), (long) allResults.size(), results.iterator());
+ }
+
+ // Constructor for DAO -> API bridge
+ public DefaultPagination(final Pagination original, final Long limit, final Iterator<T> delegate) {
+ this(original.getCurrentOffset(), limit, original.getTotalNbRecords(), original.getMaxNbRecords(), delegate);
+ }
+
+ // Constructor for DAO getAll calls
+ public DefaultPagination(final Long maxNbRecords, final Iterator<T> results) {
+ this(0L, Long.MAX_VALUE, maxNbRecords, maxNbRecords, results);
+ }
+
+ public DefaultPagination(final Long currentOffset, final Long limit,
+ @Nullable final Long totalNbRecords, @Nullable final Long maxNbRecords,
+ final Iterator<T> delegateIterator) {
+ this.currentOffset = currentOffset;
+ this.limit = limit;
+ this.totalNbRecords = totalNbRecords;
+ this.maxNbRecords = maxNbRecords;
+ this.delegateIterator = delegateIterator;
+ }
+
+ @Override
+ public Iterator<T> iterator() {
+ return delegateIterator;
+ }
+
+ @Override
+ public Long getCurrentOffset() {
+ return currentOffset;
+ }
+
+ @Override
+ public Long getNextOffset() {
+ final long candidate = currentOffset + limit;
+ if (totalNbRecords != null && candidate >= totalNbRecords) {
+ // No more results
+ return null;
+ } else {
+ // When we don't know the total number of records, the next offset
+ // returned here won't make sense once the last result is returned.
+ // It is the responsibility of the client to handle the pagination stop condition
+ // in that case (i.e. check if there is no more results).
+ return candidate;
+ }
+ }
+
+ @Override
+ public Long getMaxNbRecords() {
+ return maxNbRecords;
+ }
+
+ @Override
+ public Long getTotalNbRecords() {
+ return totalNbRecords;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("DefaultPagination{");
+ sb.append("currentOffset=").append(currentOffset);
+ sb.append(", nextOffset=").append(getNextOffset());
+ sb.append(", totalNbRecords=").append(totalNbRecords);
+ sb.append(", maxNbRecords=").append(maxNbRecords);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ // Expensive! Will compare the content of the iterator
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ final DefaultPagination that = (DefaultPagination) o;
+
+ if (totalNbRecords != null ? !totalNbRecords.equals(that.totalNbRecords) : that.totalNbRecords != null) {
+ return false;
+ }
+ if (maxNbRecords != null ? !maxNbRecords.equals(that.maxNbRecords) : that.maxNbRecords != null) {
+ return false;
+ }
+ if (currentOffset != null ? !currentOffset.equals(that.currentOffset) : that.currentOffset != null) {
+ return false;
+ }
+ if (delegateIterator != null ? !ImmutableList.<T>copyOf(delegateIterator).equals(ImmutableList.<T>copyOf(that.delegateIterator)) : that.delegateIterator != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = currentOffset != null ? currentOffset.hashCode() : 0;
+ result = 31 * result + (totalNbRecords != null ? totalNbRecords.hashCode() : 0);
+ result = 31 * result + (maxNbRecords != null ? maxNbRecords.hashCode() : 0);
+ result = 31 * result + (delegateIterator != null ? delegateIterator.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/DefaultTagDefinitionDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/DefaultTagDefinitionDao.java
index 8bf21dc..019c222 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/DefaultTagDefinitionDao.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/DefaultTagDefinitionDao.java
@@ -17,6 +17,7 @@
package com.ning.billing.util.tag.dao;
import java.util.Collection;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
@@ -29,25 +30,26 @@ import org.slf4j.LoggerFactory;
import com.ning.billing.BillingExceptionBase;
import com.ning.billing.ErrorCode;
import com.ning.billing.bus.api.PersistentBus;
-import com.ning.billing.util.api.TagDefinitionApiException;
-import com.ning.billing.util.audit.ChangeType;
-import com.ning.billing.util.cache.CacheControllerDispatcher;
import com.ning.billing.callcontext.InternalCallContext;
import com.ning.billing.callcontext.InternalTenantContext;
import com.ning.billing.clock.Clock;
+import com.ning.billing.events.TagDefinitionInternalEvent;
+import com.ning.billing.util.api.TagDefinitionApiException;
+import com.ning.billing.util.audit.ChangeType;
+import com.ning.billing.util.cache.CacheControllerDispatcher;
import com.ning.billing.util.dao.NonEntityDao;
import com.ning.billing.util.entity.dao.EntityDaoBase;
import com.ning.billing.util.entity.dao.EntitySqlDao;
import com.ning.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
import com.ning.billing.util.entity.dao.EntitySqlDaoTransactionalJdbiWrapper;
import com.ning.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
-import com.ning.billing.events.TagDefinitionInternalEvent;
import com.ning.billing.util.tag.ControlTagType;
import com.ning.billing.util.tag.TagDefinition;
import com.ning.billing.util.tag.api.user.TagEventBuilder;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
+import com.google.common.collect.Iterators;
import com.google.inject.Inject;
public class DefaultTagDefinitionDao extends EntityDaoBase<TagDefinitionModelDao, TagDefinition, TagDefinitionApiException> implements TagDefinitionDao {
@@ -71,8 +73,10 @@ public class DefaultTagDefinitionDao extends EntityDaoBase<TagDefinitionModelDao
@Override
public List<TagDefinitionModelDao> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
// Get user definitions from the database
+ final TagDefinitionSqlDao tagDefinitionSqlDao = entitySqlDaoWrapperFactory.become(TagDefinitionSqlDao.class);
+ final Iterator<TagDefinitionModelDao> all = tagDefinitionSqlDao.getAll(context);
final List<TagDefinitionModelDao> definitionList = new LinkedList<TagDefinitionModelDao>();
- definitionList.addAll(entitySqlDaoWrapperFactory.become(TagDefinitionSqlDao.class).get(context));
+ Iterators.addAll(definitionList, all);
// Add control tag definitions
for (final ControlTagType controlTag : ControlTagType.values()) {
@@ -240,7 +244,7 @@ public class DefaultTagDefinitionDao extends EntityDaoBase<TagDefinitionModelDao
tagEventBuilder.newControlTagDefinitionCreationEvent(tagDefinition.getId(), tagDefinition,
context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken()) :
tagEventBuilder.newUserTagDefinitionCreationEvent(tagDefinition.getId(), tagDefinition,
- context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken());
+ context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken());
break;
case DELETE:
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/TagDefinitionDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/TagDefinitionDao.java
index 76dcceb..6e9e6c4 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/TagDefinitionDao.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagDefinitionDao.java
@@ -20,16 +20,16 @@ import java.util.Collection;
import java.util.List;
import java.util.UUID;
-import com.ning.billing.util.api.TagDefinitionApiException;
import com.ning.billing.callcontext.InternalCallContext;
import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.util.api.TagDefinitionApiException;
+import com.ning.billing.util.entity.dao.EntityDao;
+import com.ning.billing.util.tag.TagDefinition;
-public interface TagDefinitionDao {
+public interface TagDefinitionDao extends EntityDao<TagDefinitionModelDao, TagDefinition, TagDefinitionApiException> {
public List<TagDefinitionModelDao> getTagDefinitions(InternalTenantContext context);
- public TagDefinitionModelDao getById(UUID definitionId, InternalTenantContext context);
-
public TagDefinitionModelDao getByName(String definitionName, InternalTenantContext context);
public List<TagDefinitionModelDao> getByIds(Collection<UUID> definitionIds, InternalTenantContext context);
diff --git a/util/src/main/resources/com/ning/billing/util/entity/dao/EntitySqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/entity/dao/EntitySqlDao.sql.stg
index 5ee87d6..1bc6a90 100644
--- a/util/src/main/resources/com/ning/billing/util/entity/dao/EntitySqlDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/entity/dao/EntitySqlDao.sql.stg
@@ -133,13 +133,36 @@ allHistoryTableValues() ::= <<
CHECK_TENANT(prefix) ::= "<prefix>tenant_record_id = :tenantRecordId"
AND_CHECK_TENANT(prefix) ::= "and <CHECK_TENANT(prefix)>"
-get(limit) ::= <<
+getFoundRows() ::= <<
+select FOUND_ROWS();
+>>
+
+getAll() ::= <<
select
<allTableFields("t.")>
from <tableName()> t
where <CHECK_TENANT("t.")>
<andCheckSoftDeletionWithComma("t.")>
-<if(limit)>limit :limit<endif>
+;
+>>
+
+get(offset, rowCount, orderBy) ::= <<
+select SQL_CALC_FOUND_ROWS
+<allTableFields("t.")>
+from <tableName()> t
+where <CHECK_TENANT("t.")>
+<andCheckSoftDeletionWithComma("t.")>
+order by :orderBy
+limit :offset, :rowCount
+;
+>>
+
+getCount() ::= <<
+select
+count(1) as count
+from <tableName()> t
+where <CHECK_TENANT("t.")>
+<andCheckSoftDeletionWithComma("t.")>
;
>>
diff --git a/util/src/test/java/com/ning/billing/mock/api/MockAccountUserApi.java b/util/src/test/java/com/ning/billing/mock/api/MockAccountUserApi.java
index 488b8d8..c29d635 100644
--- a/util/src/test/java/com/ning/billing/mock/api/MockAccountUserApi.java
+++ b/util/src/test/java/com/ning/billing/mock/api/MockAccountUserApi.java
@@ -16,7 +16,6 @@
package com.ning.billing.mock.api;
-import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
@@ -33,6 +32,8 @@ import com.ning.billing.catalog.api.Currency;
import com.ning.billing.mock.MockAccountBuilder;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.TenantContext;
+import com.ning.billing.util.entity.DefaultPagination;
+import com.ning.billing.util.entity.Pagination;
public class MockAccountUserApi implements AccountUserApi {
@@ -107,7 +108,7 @@ public class MockAccountUserApi implements AccountUserApi {
}
@Override
- public List<Account> searchAccounts(final String searchKey, final TenantContext tenantContext) {
+ public Pagination<Account> searchAccounts(final String searchKey, final Long offset, final Long limit, final TenantContext tenantContext) {
final List<Account> results = new LinkedList<Account>();
for (final Account account : accounts) {
if ((account.getName() != null && account.getName().contains(searchKey)) ||
@@ -117,12 +118,12 @@ public class MockAccountUserApi implements AccountUserApi {
results.add(account);
}
}
- return results;
+ return DefaultPagination.<Account>build(offset, limit, results);
}
@Override
- public List<Account> getAccounts(final TenantContext context) {
- return new ArrayList<Account>(accounts);
+ public Pagination<Account> getAccounts(final Long offset, final Long limit, final TenantContext context) {
+ return DefaultPagination.<Account>build(offset, limit, accounts);
}
@Override
diff --git a/util/src/test/java/com/ning/billing/util/customfield/dao/MockCustomFieldDao.java b/util/src/test/java/com/ning/billing/util/customfield/dao/MockCustomFieldDao.java
index 0f7f467..8521cb9 100644
--- a/util/src/test/java/com/ning/billing/util/customfield/dao/MockCustomFieldDao.java
+++ b/util/src/test/java/com/ning/billing/util/customfield/dao/MockCustomFieldDao.java
@@ -21,8 +21,8 @@ import java.util.List;
import java.util.UUID;
import com.ning.billing.ObjectType;
-import com.ning.billing.util.api.CustomFieldApiException;
import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.util.api.CustomFieldApiException;
import com.ning.billing.util.customfield.CustomField;
import com.ning.billing.util.entity.dao.MockEntityDaoBase;
@@ -31,7 +31,7 @@ public class MockCustomFieldDao extends MockEntityDaoBase<CustomFieldModelDao, C
@Override
public List<CustomFieldModelDao> getCustomFieldsForObject(final UUID objectId, final ObjectType objectType, final InternalTenantContext context) {
final List<CustomFieldModelDao> result = new ArrayList<CustomFieldModelDao>();
- final List<CustomFieldModelDao> all = get(context);
+ final Iterable<CustomFieldModelDao> all = getAll(context);
for (final CustomFieldModelDao cur : all) {
if (cur.getObjectId().equals(objectId) && cur.getObjectType() == objectType) {
result.add(cur);
diff --git a/util/src/test/java/com/ning/billing/util/dao/TestPagination.java b/util/src/test/java/com/ning/billing/util/dao/TestPagination.java
new file mode 100644
index 0000000..0f0832a
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/dao/TestPagination.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2010-2013 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.List;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.util.UtilTestSuiteWithEmbeddedDB;
+import com.ning.billing.util.tag.dao.TagDefinitionModelDao;
+import com.ning.billing.util.tag.dao.TagDefinitionSqlDao;
+
+import com.google.common.collect.ImmutableList;
+
+public class TestPagination extends UtilTestSuiteWithEmbeddedDB {
+
+ @Test(groups = "slow", description = "Test Pagination: basic SqlDAO and DAO calls")
+ public void testTagDefinitionsPagination() throws Exception {
+ final TagDefinitionSqlDao tagDefinitionSqlDao = dbi.onDemand(TagDefinitionSqlDao.class);
+
+ for (int i = 0; i < 10; i++) {
+ final String definitionName = "name-" + i;
+ final String description = "description-" + i;
+ tagDefinitionDao.create(definitionName, description, internalCallContext);
+ }
+
+ // Tests via SQL dao directly
+ Assert.assertEquals(ImmutableList.<TagDefinitionModelDao>copyOf(tagDefinitionSqlDao.getAll(internalCallContext)).size(), 10);
+ Assert.assertEquals(ImmutableList.<TagDefinitionModelDao>copyOf(tagDefinitionSqlDao.get(0L, 100L, "recordId", internalCallContext)).size(), 10);
+ Assert.assertEquals(ImmutableList.<TagDefinitionModelDao>copyOf(tagDefinitionSqlDao.get(5L, 100L, "recordId", internalCallContext)).size(), 5);
+ Assert.assertEquals(ImmutableList.<TagDefinitionModelDao>copyOf(tagDefinitionSqlDao.get(5L, 10L, "recordId", internalCallContext)).size(), 5);
+ Assert.assertEquals(ImmutableList.<TagDefinitionModelDao>copyOf(tagDefinitionSqlDao.get(0L, 5L, "recordId", internalCallContext)).size(), 5);
+ for (int i = 0; i < 10; i++) {
+ final List<TagDefinitionModelDao> tagDefinitions = ImmutableList.<TagDefinitionModelDao>copyOf(tagDefinitionSqlDao.get(0L, (long) i, "recordId", internalCallContext));
+ Assert.assertEquals(tagDefinitions.size(), i);
+
+ for (int j = 0; j < tagDefinitions.size(); j++) {
+ Assert.assertEquals(tagDefinitions.get(j).getName(), "name-" + j);
+ Assert.assertEquals(tagDefinitions.get(j).getDescription(), "description-" + j);
+ }
+ }
+
+ // Tests via DAO (to test EntityDaoBase)
+ Assert.assertEquals(ImmutableList.<TagDefinitionModelDao>copyOf(tagDefinitionDao.getAll(internalCallContext)).size(), 10);
+ Assert.assertEquals(ImmutableList.<TagDefinitionModelDao>copyOf(tagDefinitionDao.get(0L, 100L, internalCallContext)).size(), 10);
+ Assert.assertEquals(ImmutableList.<TagDefinitionModelDao>copyOf(tagDefinitionDao.get(5L, 100L, internalCallContext)).size(), 5);
+ Assert.assertEquals(ImmutableList.<TagDefinitionModelDao>copyOf(tagDefinitionDao.get(5L, 10L, internalCallContext)).size(), 5);
+ Assert.assertEquals(ImmutableList.<TagDefinitionModelDao>copyOf(tagDefinitionDao.get(0L, 5L, internalCallContext)).size(), 5);
+ for (int i = 0; i < 10; i++) {
+ final List<TagDefinitionModelDao> tagDefinitions = ImmutableList.<TagDefinitionModelDao>copyOf(tagDefinitionDao.get(0L, (long) i, internalCallContext));
+ Assert.assertEquals(tagDefinitions.size(), i);
+
+ for (int j = 0; j < tagDefinitions.size(); j++) {
+ Assert.assertEquals(tagDefinitions.get(j).getName(), "name-" + j);
+ Assert.assertEquals(tagDefinitions.get(j).getDescription(), "description-" + j);
+ }
+ }
+ }
+}
diff --git a/util/src/test/java/com/ning/billing/util/dao/TestStringTemplateInheritance.java b/util/src/test/java/com/ning/billing/util/dao/TestStringTemplateInheritance.java
index 4daf73a..1306cfe 100644
--- a/util/src/test/java/com/ning/billing/util/dao/TestStringTemplateInheritance.java
+++ b/util/src/test/java/com/ning/billing/util/dao/TestStringTemplateInheritance.java
@@ -102,29 +102,30 @@ public class TestStringTemplateInheritance extends UtilTestSuiteNoDB {
"where t.target_record_id = :targetRecordId\n" +
"and t.tenant_record_id = :tenantRecordId\n" +
";");
- Assert.assertEquals(kombucha.getInstanceOf("get").toString(), "select\n" +
- " t.record_id\n" +
- ", t.id\n" +
- ", t.tea\n" +
- ", t.mushroom\n" +
- ", t.sugar\n" +
- ", t.account_record_id\n" +
- ", t.tenant_record_id\n" +
- "from kombucha t\n" +
- "where t.tenant_record_id = :tenantRecordId\n" +
- ";");
- Assert.assertEquals(kombucha.getInstanceOf("get", ImmutableMap.<String, String>of("limit", "12")).toString(), "select\n" +
- " t.record_id\n" +
- ", t.id\n" +
- ", t.tea\n" +
- ", t.mushroom\n" +
- ", t.sugar\n" +
- ", t.account_record_id\n" +
- ", t.tenant_record_id\n" +
- "from kombucha t\n" +
- "where t.tenant_record_id = :tenantRecordId\n" +
- "limit :limit\n" +
- ";");
+ Assert.assertEquals(kombucha.getInstanceOf("getAll").toString(), "select\n" +
+ " t.record_id\n" +
+ ", t.id\n" +
+ ", t.tea\n" +
+ ", t.mushroom\n" +
+ ", t.sugar\n" +
+ ", t.account_record_id\n" +
+ ", t.tenant_record_id\n" +
+ "from kombucha t\n" +
+ "where t.tenant_record_id = :tenantRecordId\n" +
+ ";");
+ Assert.assertEquals(kombucha.getInstanceOf("get", ImmutableMap.<String, String>of("orderBy", "recordId", "offset", "3", "rowCount", "12")).toString(), "select SQL_CALC_FOUND_ROWS\n" +
+ " t.record_id\n" +
+ ", t.id\n" +
+ ", t.tea\n" +
+ ", t.mushroom\n" +
+ ", t.sugar\n" +
+ ", t.account_record_id\n" +
+ ", t.tenant_record_id\n" +
+ "from kombucha t\n" +
+ "where t.tenant_record_id = :tenantRecordId\n" +
+ "order by :orderBy\n" +
+ "limit :offset, :rowCount\n" +
+ ";");
Assert.assertEquals(kombucha.getInstanceOf("test").toString(), "select\n" +
" t.record_id\n" +
", t.id\n" +
diff --git a/util/src/test/java/com/ning/billing/util/dao/TestStringTemplateInheritanceWithJdbi.java b/util/src/test/java/com/ning/billing/util/dao/TestStringTemplateInheritanceWithJdbi.java
index 24abcd5..0b2cad2 100644
--- a/util/src/test/java/com/ning/billing/util/dao/TestStringTemplateInheritanceWithJdbi.java
+++ b/util/src/test/java/com/ning/billing/util/dao/TestStringTemplateInheritanceWithJdbi.java
@@ -20,7 +20,6 @@ import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.testng.Assert;
import org.testng.annotations.Test;
-import com.ning.billing.GuicyKillbillTestSuiteWithEmbeddedDB;
import com.ning.billing.util.UtilTestSuiteWithEmbeddedDB;
import com.ning.billing.util.entity.Entity;
import com.ning.billing.util.entity.dao.EntityModelDao;
@@ -48,6 +47,6 @@ public class TestStringTemplateInheritanceWithJdbi extends UtilTestSuiteWithEmbe
Assert.assertEquals(dao.isIsTimeForKombucha(), clock.getUTCNow().getHourOfDay() == 17);
// Verify inherited templates
- Assert.assertEquals(dao.get(internalCallContext).size(), 0);
+ Assert.assertFalse(dao.getAll(internalCallContext).hasNext());
}
}
diff --git a/util/src/test/java/com/ning/billing/util/entity/dao/MockEntityDaoBase.java b/util/src/test/java/com/ning/billing/util/entity/dao/MockEntityDaoBase.java
index b7ba4a2..0aabc34 100644
--- a/util/src/test/java/com/ning/billing/util/entity/dao/MockEntityDaoBase.java
+++ b/util/src/test/java/com/ning/billing/util/entity/dao/MockEntityDaoBase.java
@@ -26,8 +26,11 @@ import java.util.concurrent.atomic.AtomicLong;
import com.ning.billing.BillingExceptionBase;
import com.ning.billing.callcontext.InternalCallContext;
import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.util.entity.DefaultPagination;
import com.ning.billing.util.entity.Entity;
+import com.ning.billing.util.entity.Pagination;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
public class MockEntityDaoBase<M extends EntityModelDao<E>, E extends Entity, U extends BillingExceptionBase> implements EntityDao<M, E, U> {
@@ -62,12 +65,22 @@ public class MockEntityDaoBase<M extends EntityModelDao<E>, E extends Entity, U
}
@Override
- public List<M> get(final InternalTenantContext context) {
+ public Pagination<M> getAll(final InternalTenantContext context) {
final List<M> result = new ArrayList<M>();
for (final Map<Long, M> cur : entities.values()) {
result.add(cur.values().iterator().next());
}
- return result;
+ return new DefaultPagination<M>(getCount(context), result.iterator());
+ }
+
+ @Override
+ public Pagination<M> get(final Long offset, final Long limit, final InternalTenantContext context) {
+ return DefaultPagination.<M>build(offset, limit, ImmutableList.<M>copyOf(getAll(context)));
+ }
+
+ @Override
+ public Long getCount(final InternalTenantContext context) {
+ return (long) entities.keySet().size();
}
public void update(final M entity, final InternalCallContext context) {
diff --git a/util/src/test/java/com/ning/billing/util/entity/TestDefaultPagination.java b/util/src/test/java/com/ning/billing/util/entity/TestDefaultPagination.java
new file mode 100644
index 0000000..2600ff1
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/entity/TestDefaultPagination.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2010-2013 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.entity;
+
+import java.util.List;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.util.UtilTestSuiteNoDB;
+
+import com.google.common.collect.ImmutableList;
+
+public class TestDefaultPagination extends UtilTestSuiteNoDB {
+
+ @Test(groups = "fast", description = "Test Util: Pagination builder tests")
+ public void testDefaultPaginationBuilder() throws Exception {
+ Assert.assertEquals(DefaultPagination.<Integer>build(0L, 0L, ImmutableList.<Integer>of()), expectedOf(0L, 0L, 0L, ImmutableList.<Integer>of()));
+ Assert.assertEquals(DefaultPagination.<Integer>build(0L, 10L, ImmutableList.<Integer>of()), expectedOf(0L, 0L, 0L, ImmutableList.<Integer>of()));
+ Assert.assertEquals(DefaultPagination.<Integer>build(10L, 0L, ImmutableList.<Integer>of()), expectedOf(10L, 0L, 0L, ImmutableList.<Integer>of()));
+ Assert.assertEquals(DefaultPagination.<Integer>build(10L, 10L, ImmutableList.<Integer>of()), expectedOf(10L, 0L, 0L, ImmutableList.<Integer>of()));
+
+ Assert.assertEquals(DefaultPagination.<Integer>build(0L, 0L, ImmutableList.<Integer>of(1, 2, 3, 4, 5)), expectedOf(0L, 0L, 5L, ImmutableList.<Integer>of()));
+ Assert.assertEquals(DefaultPagination.<Integer>build(0L, 10L, ImmutableList.<Integer>of(1, 2, 3, 4, 5)), expectedOf(0L, 5L, 5L, ImmutableList.<Integer>of(1, 2, 3, 4, 5)));
+ Assert.assertEquals(DefaultPagination.<Integer>build(4L, 10L, ImmutableList.<Integer>of(1, 2, 3, 4, 5)), expectedOf(4L, 1L, 5L, ImmutableList.<Integer>of(5)));
+
+ Assert.assertEquals(DefaultPagination.<Integer>build(0L, 3L, ImmutableList.<Integer>of(1, 2, 3, 4, 5)), expectedOf(0L, 3L, 5L, ImmutableList.<Integer>of(1, 2, 3)));
+ Assert.assertEquals(DefaultPagination.<Integer>build(1L, 3L, ImmutableList.<Integer>of(1, 2, 3, 4, 5)), expectedOf(1L, 3L, 5L, ImmutableList.<Integer>of(2, 3, 4)));
+ Assert.assertEquals(DefaultPagination.<Integer>build(2L, 3L, ImmutableList.<Integer>of(1, 2, 3, 4, 5)), expectedOf(2L, 3L, 5L, ImmutableList.<Integer>of(3, 4, 5)));
+ Assert.assertEquals(DefaultPagination.<Integer>build(3L, 3L, ImmutableList.<Integer>of(1, 2, 3, 4, 5)), expectedOf(3L, 2L, 5L, ImmutableList.<Integer>of(4, 5)));
+ Assert.assertEquals(DefaultPagination.<Integer>build(4L, 3L, ImmutableList.<Integer>of(1, 2, 3, 4, 5)), expectedOf(4L, 1L, 5L, ImmutableList.<Integer>of(5)));
+ Assert.assertEquals(DefaultPagination.<Integer>build(5L, 3L, ImmutableList.<Integer>of(1, 2, 3, 4, 5)), expectedOf(5L, 0L, 5L, ImmutableList.<Integer>of()));
+ }
+
+ private Pagination<Integer> expectedOf(final Long currentOffset, final Long totalNbRecords,
+ final Long maxNbRecords, final List<Integer> delegate) {
+ return new DefaultPagination<Integer>(currentOffset, Long.MAX_VALUE, totalNbRecords, maxNbRecords, delegate.iterator());
+ }
+}
diff --git a/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDefinitionDao.java b/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDefinitionDao.java
index 9232b1d..592d964 100644
--- a/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDefinitionDao.java
+++ b/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDefinitionDao.java
@@ -23,11 +23,13 @@ import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
-import com.ning.billing.util.api.TagDefinitionApiException;
import com.ning.billing.callcontext.InternalCallContext;
import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.util.api.TagDefinitionApiException;
+import com.ning.billing.util.entity.dao.MockEntityDaoBase;
+import com.ning.billing.util.tag.TagDefinition;
-public class MockTagDefinitionDao implements TagDefinitionDao {
+public class MockTagDefinitionDao extends MockEntityDaoBase<TagDefinitionModelDao, TagDefinition, TagDefinitionApiException> implements TagDefinitionDao {
private final Map<String, TagDefinitionModelDao> tags = new ConcurrentHashMap<String, TagDefinitionModelDao>();
@@ -56,11 +58,6 @@ public class MockTagDefinitionDao implements TagDefinitionDao {
}
@Override
- public TagDefinitionModelDao getById(final UUID definitionId, final InternalTenantContext context) {
- return null;
- }
-
- @Override
public List<TagDefinitionModelDao> getByIds(final Collection<UUID> definitionIds, final InternalTenantContext context) {
return null;
}