killbill-aplcache
pagination: various fixes after Kaui integration * Add /1.0/kb/accounts/pagination …
10/22/2013 7:05:57 PM
Changes
Details
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 7c71ea2..5620062 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
@@ -108,17 +108,29 @@ public class DefaultAccountDao extends EntityDaoBase<AccountModelDao, Account, A
@Override
public Pagination<AccountModelDao> searchAccounts(final String searchKey, final Long offset, final Long rowCount, 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 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);
+ 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);
-
- // Note: the connection will be busy as we stream the results out: hence we cannot use
- // SQL_CALC_FOUND_ROWS / FOUND_ROWS and we don't know the total number of results (in this case,
- // performing a second time the search just to get the count is too expensive to be worth it).
- final Long count = null;
-
+ final Long totalCount = accountSqlDao.getCount(context);
final Iterator<AccountModelDao> results = accountSqlDao.searchAccounts(searchKey, offset, rowCount, context);
- return new DefaultPagination<AccountModelDao>(offset, rowCount, count, results);
+
+ return new DefaultPagination<AccountModelDao>(offset, count, totalCount, results);
}
@Override
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 561566e..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
@@ -87,7 +87,7 @@ getAccountByKey() ::= <<
>>
searchAccounts(searchKey, offset, rowCount) ::= <<
-select <allTableFields()>
+select SQL_CALC_FOUND_ROWS <allTableFields()>
from (
select <allTableFields()>
from accounts
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 c5540cb..8b107f0 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
@@ -19,6 +19,7 @@ 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;
@@ -103,6 +104,7 @@ 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.ImmutableMap;
import com.google.common.collect.Multimap;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -156,6 +158,20 @@ 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,
@@ -166,7 +182,12 @@ public class AccountResource extends JaxRsResourceBase {
@javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException {
final TenantContext tenantContext = context.createContext(request);
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 void write(final OutputStream output) throws IOException, WebApplicationException {
@@ -189,10 +210,10 @@ public class AccountResource extends JaxRsResourceBase {
.header(HDR_PAGINATION_TOTAL_NB_RESULTS, accounts.getTotalNbResults())
.header(HDR_PAGINATION_NB_RESULTS, accounts.getNbResults())
.header(HDR_PAGINATION_NB_RESULTS_FROM_OFFSET, accounts.getNbResultsFromOffset())
+ .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 07e7ac3..a7fd8d0 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
@@ -45,6 +45,7 @@ public interface JaxrsResource {
public static String HDR_PAGINATION_TOTAL_NB_RESULTS = "X-Killbill-Pagination-TotalNbResults";
public static String HDR_PAGINATION_NB_RESULTS = "X-Killbill-Pagination-NbResults";
public static String HDR_PAGINATION_NB_RESULTS_FROM_OFFSET = "X-Killbill-Pagination-NbResultsFromOffset";
+ public static String HDR_PAGINATION_NEXT_PAGE_URI = "X-Killbill-Pagination-NextPageUri";
/*
* Patterns
@@ -100,6 +101,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/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/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 d6f8fbe..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
@@ -68,6 +68,9 @@ public interface EntitySqlDao<M extends EntityModelDao<E>, E extends Entity> ext
@BindBean final InternalTenantContext context);
@SqlQuery
+ public Long getFoundRows(@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)
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 646d457..cb32b87 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,6 +133,10 @@ allHistoryTableValues() ::= <<
CHECK_TENANT(prefix) ::= "<prefix>tenant_record_id = :tenantRecordId"
AND_CHECK_TENANT(prefix) ::= "and <CHECK_TENANT(prefix)>"
+getFoundRows() ::= <<
+select FOUND_ROWS();
+>>
+
getAll() ::= <<
select
<allTableFields("t.")>