killbill-uncached
Changes
osgi-bundles/bundles/jruby/src/main/java/com/ning/billing/osgi/bundles/jruby/JRubyPaymentPlugin.java 10(+10 -0)
osgi-bundles/tests/beatrix/src/test/java/com/ning/billing/osgi/bundles/test/TestPaymentPluginApi.java 30(+30 -0)
osgi-bundles/tests/payment/src/test/java/com/ning/billing/osgi/bundles/test/TestPaymentPluginApi.java 44(+41 -3)
payment/src/main/java/com/ning/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java 24(+23 -1)
pom.xml 2(+1 -1)
Details
diff --git a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
index d1ffe58..4c935bb 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
@@ -34,15 +34,16 @@ 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.ning.billing.util.entity.dao.DefaultPaginationHelper.SourcePaginationBuilder;
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;
+import static com.ning.billing.util.entity.dao.DefaultPaginationHelper.getEntityPaginationNoException;
+
public class DefaultAccountUserApi implements AccountUserApi {
private final CallContextFactory callContextFactory;
@@ -92,30 +93,38 @@ public class DefaultAccountUserApi implements AccountUserApi {
@Override
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);
- }
- }));
+ return getEntityPaginationNoException(limit,
+ new SourcePaginationBuilder<AccountModelDao, AccountApiException>() {
+ @Override
+ public Pagination<AccountModelDao> build() {
+ return accountDao.searchAccounts(searchKey, offset, limit, internalCallContextFactory.createInternalTenantContext(context));
+ }
+ },
+ new Function<AccountModelDao, Account>() {
+ @Override
+ public Account apply(final AccountModelDao accountModelDao) {
+ return new DefaultAccount(accountModelDao);
+ }
+ }
+ );
}
@Override
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);
- }
- }));
+ return getEntityPaginationNoException(limit,
+ new SourcePaginationBuilder<AccountModelDao, AccountApiException>() {
+ @Override
+ public Pagination<AccountModelDao> build() {
+ return accountDao.get(offset, limit, internalCallContextFactory.createInternalTenantContext(context));
+ }
+ },
+ new Function<AccountModelDao, Account>() {
+ @Override
+ public Account apply(final AccountModelDao accountModelDao) {
+ return new DefaultAccount(accountModelDao);
+ }
+ }
+ );
}
@Override
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 fca3684..3ce879e 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
@@ -43,8 +43,8 @@ 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.DefaultPaginationSqlDaoHelper.PaginationIteratorBuilder;
import com.ning.billing.util.entity.dao.EntityDaoBase;
import com.ning.billing.util.entity.dao.EntitySqlDao;
import com.ning.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
@@ -108,30 +108,16 @@ public class DefaultAccountDao extends EntityDaoBase<AccountModelDao, Account, A
@Override
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 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);
+ return paginationHelper.getPagination(AccountSqlDao.class,
+ new PaginationIteratorBuilder<AccountModelDao, Account, AccountSqlDao>() {
+ @Override
+ public Iterator<AccountModelDao> build(final AccountSqlDao accountSqlDao, final Long limit) {
+ return accountSqlDao.searchAccounts(searchKey, offset, limit, context);
+ }
+ },
+ offset,
+ limit,
+ context);
}
@Override
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 d3b627f..171d337 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
@@ -56,17 +56,18 @@ import com.ning.billing.util.api.TagApiException;
import com.ning.billing.util.callcontext.CallContext;
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.ning.billing.util.entity.dao.DefaultPaginationHelper.SourcePaginationBuilder;
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;
+import static com.ning.billing.util.entity.dao.DefaultPaginationHelper.getEntityPaginationNoException;
+
public class DefaultInvoiceUserApi implements InvoiceUserApi {
private static final Logger log = LoggerFactory.getLogger(DefaultInvoiceUserApi.class);
@@ -115,17 +116,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);
- }
- }));
+ return getEntityPaginationNoException(limit,
+ new SourcePaginationBuilder<InvoiceModelDao, InvoiceApiException>() {
+ @Override
+ public Pagination<InvoiceModelDao> build() {
+ // Invoices will be shallow, i.e. won't contain items nor payments
+ return dao.get(offset, limit, internalCallContextFactory.createInternalTenantContext(context));
+ }
+ },
+ new Function<InvoiceModelDao, Invoice>() {
+ @Override
+ public Invoice apply(final InvoiceModelDao invoiceModelDao) {
+ return new DefaultInvoice(invoiceModelDao);
+ }
+ }
+ );
}
@Override
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentMethodJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentMethodJson.java
index 5321232..6a44dc1 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentMethodJson.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentMethodJson.java
@@ -33,7 +33,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
-public class PaymentMethodJson {
+public class PaymentMethodJson extends JsonBase {
private final String paymentMethodId;
private final String accountId;
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/TagJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/TagJson.java
index 7840747..ec0dd64 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/TagJson.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/TagJson.java
@@ -20,7 +20,9 @@ import java.util.List;
import javax.annotation.Nullable;
+import com.ning.billing.ObjectType;
import com.ning.billing.util.audit.AuditLog;
+import com.ning.billing.util.tag.Tag;
import com.ning.billing.util.tag.TagDefinition;
import com.fasterxml.jackson.annotation.JsonCreator;
@@ -28,20 +30,34 @@ import com.fasterxml.jackson.annotation.JsonProperty;
public class TagJson extends JsonBase {
+ private final String tagId;
+ private final ObjectType objectType;
private final String tagDefinitionId;
private final String tagDefinitionName;
@JsonCreator
- public TagJson(@JsonProperty("tagDefinitionId") final String tagDefinitionId,
+ public TagJson(@JsonProperty("tagId") final String tagId,
+ @JsonProperty("objectType") final ObjectType objectType,
+ @JsonProperty("tagDefinitionId") final String tagDefinitionId,
@JsonProperty("tagDefinitionName") final String tagDefinitionName,
@JsonProperty("auditLogs") @Nullable final List<AuditLogJson> auditLogs) {
super(auditLogs);
+ this.tagId = tagId;
+ this.objectType = objectType;
this.tagDefinitionId = tagDefinitionId;
this.tagDefinitionName = tagDefinitionName;
}
- public TagJson(final TagDefinition tagDefinition, @Nullable final List<AuditLog> auditLogs) {
- this(tagDefinition.getId().toString(), tagDefinition.getName(), toAuditLogJson(auditLogs));
+ public TagJson(final Tag tag, final TagDefinition tagDefinition, @Nullable final List<AuditLog> auditLogs) {
+ this(tag.getId().toString(), tag.getObjectType(), tagDefinition.getId().toString(), tagDefinition.getName(), toAuditLogJson(auditLogs));
+ }
+
+ public String getTagId() {
+ return tagId;
+ }
+
+ public ObjectType getObjectType() {
+ return objectType;
}
public String getTagDefinitionId() {
@@ -55,7 +71,9 @@ public class TagJson extends JsonBase {
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("TagJson{");
- sb.append("tagDefinitionId='").append(tagDefinitionId).append('\'');
+ sb.append("tagId='").append(tagId).append('\'');
+ sb.append(", objectType=").append(objectType);
+ sb.append(", tagDefinitionId='").append(tagDefinitionId).append('\'');
sb.append(", tagDefinitionName='").append(tagDefinitionName).append('\'');
sb.append('}');
return sb.toString();
@@ -72,19 +90,27 @@ public class TagJson extends JsonBase {
final TagJson tagJson = (TagJson) o;
+ if (objectType != tagJson.objectType) {
+ return false;
+ }
if (tagDefinitionId != null ? !tagDefinitionId.equals(tagJson.tagDefinitionId) : tagJson.tagDefinitionId != null) {
return false;
}
if (tagDefinitionName != null ? !tagDefinitionName.equals(tagJson.tagDefinitionName) : tagJson.tagDefinitionName != null) {
return false;
}
+ if (tagId != null ? !tagId.equals(tagJson.tagId) : tagJson.tagId != null) {
+ return false;
+ }
return true;
}
@Override
public int hashCode() {
- int result = tagDefinitionId != null ? tagDefinitionId.hashCode() : 0;
+ int result = tagId != null ? tagId.hashCode() : 0;
+ result = 31 * result + (objectType != null ? objectType.hashCode() : 0);
+ result = 31 * result + (tagDefinitionId != null ? tagDefinitionId.hashCode() : 0);
result = 31 * result + (tagDefinitionName != null ? tagDefinitionName.hashCode() : 0);
return result;
}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
index eb44ad3..f1727d0 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,8 +16,6 @@
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;
@@ -39,10 +37,8 @@ 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;
@@ -96,7 +92,6 @@ 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;
@@ -163,8 +158,16 @@ 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.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);
+ final URI nextPageUri = uriBuilder.nextPage(AccountResource.class, "getAccounts", accounts.getNextOffset(), limit, ImmutableMap.<String, String>of(QUERY_ACCOUNT_WITH_BALANCE, accountWithBalance.toString(),
+ QUERY_ACCOUNT_WITH_BALANCE_AND_CBA, accountWithBalanceAndCBA.toString()));
+ return buildStreamingPaginationResponse(accounts,
+ new Function<Account, AccountJson>() {
+ @Override
+ public AccountJson apply(final Account account) {
+ return getAccount(account, accountWithBalance, accountWithBalanceAndCBA, tenantContext);
+ }
+ },
+ nextPageUri);
}
@GET
@@ -178,35 +181,17 @@ 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 {
- 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(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();
+ final URI nextPageUri = uriBuilder.nextPage(AccountResource.class, "searchAccounts", accounts.getNextOffset(), limit, ImmutableMap.<String, String>of("searchKey", searchKey,
+ QUERY_ACCOUNT_WITH_BALANCE, accountWithBalance.toString(),
+ QUERY_ACCOUNT_WITH_BALANCE_AND_CBA, accountWithBalanceAndCBA.toString()));
+ return buildStreamingPaginationResponse(accounts,
+ new Function<Account, AccountJson>() {
+ @Override
+ public AccountJson apply(final Account account) {
+ return getAccount(account, accountWithBalance, accountWithBalanceAndCBA, tenantContext);
+ }
+ },
+ nextPageUri);
}
@GET
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/CustomFieldResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/CustomFieldResource.java
new file mode 100644
index 0000000..641a74d
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/CustomFieldResource.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2010-2014 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.jaxrs.resources;
+
+import java.net.URI;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Response;
+
+import com.ning.billing.ObjectType;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.clock.Clock;
+import com.ning.billing.jaxrs.json.CustomFieldJson;
+import com.ning.billing.jaxrs.util.Context;
+import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
+import com.ning.billing.util.api.AuditUserApi;
+import com.ning.billing.util.api.CustomFieldApiException;
+import com.ning.billing.util.api.CustomFieldUserApi;
+import com.ning.billing.util.api.TagUserApi;
+import com.ning.billing.util.audit.AuditLog;
+import com.ning.billing.util.callcontext.TenantContext;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.entity.Pagination;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+
+@Singleton
+@Path(JaxrsResource.CUSTOM_FIELDS_PATH)
+public class CustomFieldResource extends JaxRsResourceBase {
+
+ @Inject
+ public CustomFieldResource(final JaxrsUriBuilder uriBuilder,
+ final TagUserApi tagUserApi,
+ final CustomFieldUserApi customFieldUserApi,
+ final AuditUserApi auditUserApi,
+ final AccountUserApi accountUserApi,
+ final Clock clock,
+ final Context context) {
+ super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, clock, context);
+ }
+
+ @GET
+ @Path("/" + PAGINATION)
+ @Produces(APPLICATION_JSON)
+ public Response getCustomFields(@QueryParam(QUERY_SEARCH_OFFSET) @DefaultValue("0") final Long offset,
+ @QueryParam(QUERY_SEARCH_LIMIT) @DefaultValue("100") final Long limit,
+ @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws CustomFieldApiException {
+ final TenantContext tenantContext = context.createContext(request);
+ final Pagination<CustomField> customFields = customFieldUserApi.getCustomFields(offset, limit, tenantContext);
+ final URI nextPageUri = uriBuilder.nextPage(CustomFieldResource.class, "getCustomFields", customFields.getNextOffset(), limit, ImmutableMap.<String, String>of(QUERY_AUDIT, auditMode.toString()));
+
+ return buildStreamingPaginationResponse(customFields,
+ new Function<CustomField, CustomFieldJson>() {
+ @Override
+ public CustomFieldJson apply(final CustomField customField) {
+ // TODO Really slow - we should instead try to figure out the account id
+ final List<AuditLog> auditLogs = auditUserApi.getAuditLogs(customField.getId(), ObjectType.CUSTOM_FIELD, auditMode.getLevel(), tenantContext);
+ return new CustomFieldJson(customField, auditLogs);
+ }
+ },
+ nextPageUri);
+ }
+
+ @GET
+ @Path("/" + SEARCH + "/{searchKey:" + ANYTHING_PATTERN + "}")
+ @Produces(APPLICATION_JSON)
+ public Response searchCustomFields(@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_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws CustomFieldApiException {
+ final TenantContext tenantContext = context.createContext(request);
+ final Pagination<CustomField> customFields = customFieldUserApi.searchCustomFields(searchKey, offset, limit, tenantContext);
+ final URI nextPageUri = uriBuilder.nextPage(CustomFieldResource.class, "searchCustomFields", customFields.getNextOffset(), limit, ImmutableMap.<String, String>of("searchKey", searchKey,
+ QUERY_AUDIT, auditMode.toString()));
+ return buildStreamingPaginationResponse(customFields,
+ new Function<CustomField, CustomFieldJson>() {
+ @Override
+ public CustomFieldJson apply(final CustomField customField) {
+ // TODO Really slow - we should instead try to figure out the account id
+ final List<AuditLog> auditLogs = auditUserApi.getAuditLogs(customField.getId(), ObjectType.CUSTOM_FIELD, auditMode.getLevel(), tenantContext);
+ return new CustomFieldJson(customField, auditLogs);
+ }
+ },
+ nextPageUri);
+ }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
index dbd5d85..44522e0 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
@@ -17,9 +17,13 @@
package com.ning.billing.jaxrs.resources;
import java.io.IOException;
+import java.net.URI;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.UUID;
+import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
@@ -71,8 +75,11 @@ import com.ning.billing.util.audit.AccountAuditLogs;
import com.ning.billing.util.audit.AccountAuditLogsForObjectType;
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.google.common.base.Objects;
+import com.google.common.collect.ImmutableMap;
import com.google.inject.Inject;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
@@ -151,6 +158,34 @@ public class InvoiceResource extends JaxRsResourceBase {
return Response.status(Status.OK).entity(invoiceApi.getInvoiceAsHTML(UUID.fromString(invoiceId), context.createContext(request))).build();
}
+ @GET
+ @Path("/" + PAGINATION)
+ @Produces(APPLICATION_JSON)
+ public Response getInvoices(@QueryParam(QUERY_SEARCH_OFFSET) @DefaultValue("0") final Long offset,
+ @QueryParam(QUERY_SEARCH_LIMIT) @DefaultValue("100") final Long limit,
+ @QueryParam(QUERY_INVOICE_WITH_ITEMS) @DefaultValue("false") final Boolean withItems,
+ @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws InvoiceApiException {
+ final TenantContext tenantContext = context.createContext(request);
+ final Pagination<Invoice> invoices = invoiceApi.getInvoices(offset, limit, tenantContext);
+ final URI nextPageUri = uriBuilder.nextPage(InvoiceResource.class, "getInvoices", invoices.getNextOffset(), limit, ImmutableMap.<String, String>of(QUERY_INVOICE_WITH_ITEMS, withItems.toString(),
+ QUERY_AUDIT, auditMode.toString()));
+
+ final AtomicReference<Map<UUID, AccountAuditLogs>> accountsAuditLogs = new AtomicReference<Map<UUID, AccountAuditLogs>>(new HashMap<UUID, AccountAuditLogs>());
+ return buildStreamingPaginationResponse(invoices,
+ new Function<Invoice, InvoiceJson>() {
+ @Override
+ public InvoiceJson apply(final Invoice invoice) {
+ // Cache audit logs per account
+ if (accountsAuditLogs.get().get(invoice.getAccountId()) == null) {
+ accountsAuditLogs.get().put(invoice.getAccountId(), auditUserApi.getAccountAuditLogs(invoice.getAccountId(), auditMode.getLevel(), tenantContext));
+ }
+ return new InvoiceJson(invoice, withItems, accountsAuditLogs.get().get(invoice.getAccountId()));
+ }
+ },
+ nextPageUri);
+ }
+
@POST
@Consumes(APPLICATION_JSON)
@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 0234816..71d8418 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
@@ -148,7 +148,11 @@ public interface JaxrsResource {
public static final String CHARGEBACKS_PATH = PREFIX + "/" + CHARGEBACKS;
public static final String TAGS = "tags";
+ public static final String TAGS_PATH = PREFIX + "/" + TAGS;
+
public static final String CUSTOM_FIELDS = "customFields";
+ public static final String CUSTOM_FIELDS_PATH = PREFIX + "/" + CUSTOM_FIELDS;
+
public static final String EMAILS = "emails";
public static final String EMAIL_NOTIFICATIONS = "emailNotifications";
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 e583cd4..5a265fd 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
@@ -16,6 +16,9 @@
package com.ning.billing.jaxrs.resources;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URI;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
@@ -23,7 +26,10 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
+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 org.joda.time.DateTime;
@@ -41,6 +47,7 @@ import com.ning.billing.account.api.AccountApiException;
import com.ning.billing.account.api.AccountUserApi;
import com.ning.billing.clock.Clock;
import com.ning.billing.jaxrs.json.CustomFieldJson;
+import com.ning.billing.jaxrs.json.JsonBase;
import com.ning.billing.jaxrs.json.TagJson;
import com.ning.billing.jaxrs.util.Context;
import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
@@ -56,10 +63,13 @@ 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.entity.Entity;
+import com.ning.billing.util.entity.Pagination;
import com.ning.billing.util.jackson.ObjectMapper;
import com.ning.billing.util.tag.Tag;
import com.ning.billing.util.tag.TagDefinition;
+import com.fasterxml.jackson.core.JsonGenerator;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
@@ -114,7 +124,7 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
final TagDefinition tagDefinition = tagDefinitionsCache.get(tag.getTagDefinitionId());
final List<AuditLog> auditLogs = tagsAuditLogs.getAuditLogs(tag.getId());
- result.add(new TagJson(tagDefinition, auditLogs));
+ result.add(new TagJson(tag, tagDefinition, auditLogs));
}
return Response.status(Response.Status.OK).entity(result).build();
@@ -181,6 +191,34 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
return Response.status(Response.Status.OK).build();
}
+ protected <E extends Entity, J extends JsonBase> Response buildStreamingPaginationResponse(final Pagination<E> entities,
+ final Function<E, J> toJson,
+ final URI nextPageUri) {
+ final StreamingOutput json = new StreamingOutput() {
+ @Override
+ 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 E entity : entities) {
+ generator.writeObject(toJson.apply(entity));
+ }
+ generator.writeEndArray();
+ generator.close();
+ }
+ };
+
+ return Response.status(Status.OK)
+ .entity(json)
+ .header(HDR_PAGINATION_CURRENT_OFFSET, entities.getCurrentOffset())
+ .header(HDR_PAGINATION_NEXT_OFFSET, entities.getNextOffset())
+ .header(HDR_PAGINATION_TOTAL_NB_RECORDS, entities.getTotalNbRecords())
+ .header(HDR_PAGINATION_MAX_NB_RECORDS, entities.getMaxNbRecords())
+ .header(HDR_PAGINATION_NEXT_PAGE_URI, nextPageUri)
+ .build();
+ }
+
protected LocalDate toLocalDate(final UUID accountId, final String inputDate, final TenantContext context) {
final LocalDate maybeResult = extractLocalDate(inputDate);
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 f273d0a..de740e4 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,12 +16,11 @@
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.Map;
import java.util.UUID;
+import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.DELETE;
@@ -32,10 +31,8 @@ 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;
@@ -51,11 +48,12 @@ import com.ning.billing.payment.api.PaymentMethod;
import com.ning.billing.util.api.AuditUserApi;
import com.ning.billing.util.api.CustomFieldUserApi;
import com.ning.billing.util.api.TagUserApi;
+import com.ning.billing.util.audit.AccountAuditLogs;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.TenantContext;
import com.ning.billing.util.entity.Pagination;
-import com.fasterxml.jackson.core.JsonGenerator;
+import com.google.common.base.Function;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.inject.Inject;
@@ -103,20 +101,48 @@ public class PaymentMethodResource extends JaxRsResourceBase {
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,
+ @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
@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);
+ final URI nextPageUri = uriBuilder.nextPage(PaymentMethodResource.class, "getPaymentMethods", paymentMethods.getNextOffset(), limit, ImmutableMap.<String, String>of(QUERY_PAYMENT_METHOD_PLUGIN_NAME, Strings.nullToEmpty(pluginName),
+ QUERY_AUDIT, auditMode.toString()));
+
+ final AtomicReference<Map<UUID, AccountAuditLogs>> accountsAuditLogs = new AtomicReference<Map<UUID, AccountAuditLogs>>(new HashMap<UUID, AccountAuditLogs>());
+ final Map<UUID, Account> accounts = new HashMap<UUID, Account>();
+ return buildStreamingPaginationResponse(paymentMethods,
+ new Function<PaymentMethod, PaymentMethodJson>() {
+ @Override
+ public PaymentMethodJson apply(final PaymentMethod paymentMethod) {
+ // Cache audit logs per account
+ if (accountsAuditLogs.get().get(paymentMethod.getAccountId()) == null) {
+ accountsAuditLogs.get().put(paymentMethod.getAccountId(), auditUserApi.getAccountAuditLogs(paymentMethod.getAccountId(), auditMode.getLevel(), tenantContext));
+ }
+
+ // 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 (final AccountApiException e) {
+ log.warn("Unable to retrieve account", e);
+ return null;
+ }
+ }
+
+ // TODO populate audit logs
+ return PaymentMethodJson.toPaymentMethodJson(accounts.get(paymentMethod.getAccountId()), paymentMethod);
+ }
+ },
+ nextPageUri);
}
@GET
@@ -126,6 +152,7 @@ public class PaymentMethodResource extends JaxRsResourceBase {
@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,
+ @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
@javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
final TenantContext tenantContext = context.createContext(request);
@@ -137,47 +164,38 @@ public class PaymentMethodResource extends JaxRsResourceBase {
paymentMethods = paymentApi.searchPaymentMethods(searchKey, offset, limit, pluginName, tenantContext);
}
- final URI nextPageUri = uriBuilder.nextPage(PaymentMethodResource.class, "searchPaymentMethods", paymentMethods.getNextOffset(), limit, ImmutableMap.<String, String>of());
- return buildStreamingPaymentMethodsResponse(paymentMethods, nextPageUri, tenantContext);
- }
+ final URI nextPageUri = uriBuilder.nextPage(PaymentMethodResource.class, "searchPaymentMethods", paymentMethods.getNextOffset(), limit, ImmutableMap.<String, String>of("searchKey", searchKey,
+ QUERY_PAYMENT_METHOD_PLUGIN_NAME, Strings.nullToEmpty(pluginName),
+ QUERY_AUDIT, auditMode.toString()));
- private Response buildStreamingPaymentMethodsResponse(final Pagination<PaymentMethod> paymentMethods, final URI nextPageUri, final TenantContext tenantContext) {
+ final AtomicReference<Map<UUID, AccountAuditLogs>> accountsAuditLogs = new AtomicReference<Map<UUID, AccountAuditLogs>>(new HashMap<UUID, AccountAuditLogs>());
final Map<UUID, Account> accounts = new HashMap<UUID, Account>();
- final StreamingOutput json = new StreamingOutput() {
- @Override
- 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) {
- log.warn("Unable to retrieve account", e);
- continue;
- }
- }
-
- final PaymentMethodJson asJson = PaymentMethodJson.toPaymentMethodJson(accounts.get(paymentMethod.getAccountId()), paymentMethod);
- generator.writeObject(asJson);
- }
- generator.writeEndArray();
- generator.close();
- }
- };
- 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();
+ return buildStreamingPaginationResponse(paymentMethods,
+ new Function<PaymentMethod, PaymentMethodJson>() {
+ @Override
+ public PaymentMethodJson apply(final PaymentMethod paymentMethod) {
+ // Cache audit logs per account
+ if (accountsAuditLogs.get().get(paymentMethod.getAccountId()) == null) {
+ accountsAuditLogs.get().put(paymentMethod.getAccountId(), auditUserApi.getAccountAuditLogs(paymentMethod.getAccountId(), auditMode.getLevel(), tenantContext));
+ }
+
+ // 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 (final AccountApiException e) {
+ log.warn("Unable to retrieve account", e);
+ return null;
+ }
+ }
+
+ // TODO populate audit logs
+ return PaymentMethodJson.toPaymentMethodJson(accounts.get(paymentMethod.getAccountId()), paymentMethod);
+ }
+ },
+ nextPageUri);
}
@DELETE
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java
index 2856f16..73ac13a 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java
@@ -16,8 +16,6 @@
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;
@@ -38,10 +36,8 @@ 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.ObjectType;
@@ -73,7 +69,6 @@ import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.TenantContext;
import com.ning.billing.util.entity.Pagination;
-import com.fasterxml.jackson.core.JsonGenerator;
import com.google.common.base.Function;
import com.google.common.base.Strings;
import com.google.common.collect.Collections2;
@@ -158,7 +153,15 @@ public class PaymentResource extends JaxRsResourceBase {
}
final URI nextPageUri = uriBuilder.nextPage(PaymentResource.class, "getPayments", payments.getNextOffset(), limit, nextUriParams);
- return buildStreamingPaymentsResponse(payments, nextPageUri);
+
+ return buildStreamingPaginationResponse(payments,
+ new Function<Payment, PaymentJson>() {
+ @Override
+ public PaymentJson apply(final Payment payment) {
+ return new PaymentJson(payment, null);
+ }
+ },
+ nextPageUri);
}
@GET
@@ -168,7 +171,7 @@ public class PaymentResource extends JaxRsResourceBase {
@QueryParam(QUERY_SEARCH_OFFSET) @DefaultValue("0") final Long offset,
@QueryParam(QUERY_SEARCH_LIMIT) @DefaultValue("100") final Long limit,
@QueryParam(QUERY_PAYMENT_PLUGIN_NAME) final String pluginName,
- @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException {
final TenantContext tenantContext = context.createContext(request);
// Search the plugin(s)
@@ -180,33 +183,15 @@ public class PaymentResource extends JaxRsResourceBase {
}
final URI nextPageUri = uriBuilder.nextPage(PaymentResource.class, "searchPayments", payments.getNextOffset(), limit, ImmutableMap.<String, String>of());
- return buildStreamingPaymentsResponse(payments, nextPageUri);
- }
- private Response buildStreamingPaymentsResponse(final Pagination<Payment> payments, final URI nextPageUri) {
- final StreamingOutput json = new StreamingOutput() {
- @Override
- 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 Payment payment : payments) {
- final PaymentJson asJson = new PaymentJson(payment, null);
- generator.writeObject(asJson);
- }
- generator.writeEndArray();
- generator.close();
- }
- };
- return Response.status(Status.OK)
- .entity(json)
- .header(HDR_PAGINATION_CURRENT_OFFSET, payments.getCurrentOffset())
- .header(HDR_PAGINATION_NEXT_OFFSET, payments.getNextOffset())
- .header(HDR_PAGINATION_TOTAL_NB_RECORDS, payments.getTotalNbRecords())
- .header(HDR_PAGINATION_MAX_NB_RECORDS, payments.getMaxNbRecords())
- .header(HDR_PAGINATION_NEXT_PAGE_URI, nextPageUri)
- .build();
+ return buildStreamingPaginationResponse(payments,
+ new Function<Payment, PaymentJson>() {
+ @Override
+ public PaymentJson apply(final Payment payment) {
+ return new PaymentJson(payment, null);
+ }
+ },
+ nextPageUri);
}
@PUT
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/RefundResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/RefundResource.java
index c8d4c05..7790be3 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/RefundResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/RefundResource.java
@@ -16,13 +16,20 @@
package com.ning.billing.jaxrs.resources;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import java.util.UUID;
+import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
@@ -35,10 +42,18 @@ import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
import com.ning.billing.payment.api.PaymentApi;
import com.ning.billing.payment.api.PaymentApiException;
import com.ning.billing.payment.api.Refund;
+import com.ning.billing.util.api.AuditLevel;
import com.ning.billing.util.api.AuditUserApi;
import com.ning.billing.util.api.CustomFieldUserApi;
import com.ning.billing.util.api.TagUserApi;
+import com.ning.billing.util.audit.AccountAuditLogs;
+import com.ning.billing.util.audit.AuditLog;
+import com.ning.billing.util.callcontext.TenantContext;
+import com.ning.billing.util.entity.Pagination;
+import com.google.common.base.Function;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
import com.google.inject.Inject;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
@@ -65,10 +80,112 @@ public class RefundResource extends JaxRsResourceBase {
@Path("/{refundId:" + UUID_PATTERN + "}")
@Produces(APPLICATION_JSON)
public Response getRefund(@PathParam("refundId") final String refundId,
+ @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
@javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException {
- final Refund refund = paymentApi.getRefund(UUID.fromString(refundId), false, context.createContext(request));
- // TODO Return adjusted items and audits
- return Response.status(Status.OK).entity(new RefundJson(refund, null, null)).build();
+ final TenantContext tenantContext = context.createContext(request);
+ final Refund refund = paymentApi.getRefund(UUID.fromString(refundId), false, tenantContext);
+ final List<AuditLog> auditLogs = auditUserApi.getAuditLogs(refund.getId(), ObjectType.REFUND, auditMode.getLevel(), tenantContext);
+ // TODO Return adjusted items
+ return Response.status(Status.OK).entity(new RefundJson(refund, null, auditLogs)).build();
+ }
+
+ @GET
+ @Path("/" + PAGINATION)
+ @Produces(APPLICATION_JSON)
+ public Response getRefunds(@QueryParam(QUERY_SEARCH_OFFSET) @DefaultValue("0") final Long offset,
+ @QueryParam(QUERY_SEARCH_LIMIT) @DefaultValue("100") final Long limit,
+ @QueryParam(QUERY_PAYMENT_PLUGIN_NAME) final String pluginName,
+ @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException {
+ final TenantContext tenantContext = context.createContext(request);
+
+ final Pagination<Refund> refunds;
+ if (Strings.isNullOrEmpty(pluginName)) {
+ refunds = paymentApi.getRefunds(offset, limit, tenantContext);
+ } else {
+ refunds = paymentApi.getRefunds(offset, limit, pluginName, tenantContext);
+ }
+
+ final URI nextPageUri = uriBuilder.nextPage(RefundResource.class, "getRefunds", refunds.getNextOffset(), limit, ImmutableMap.<String, String>of(QUERY_PAYMENT_METHOD_PLUGIN_NAME, Strings.nullToEmpty(pluginName),
+ QUERY_AUDIT, auditMode.toString()));
+
+ final AtomicReference<Map<UUID, AccountAuditLogs>> accountsAuditLogs = new AtomicReference<Map<UUID, AccountAuditLogs>>(new HashMap<UUID, AccountAuditLogs>());
+ final Map<UUID, UUID> paymentIdAccountIdMappings = new HashMap<UUID, UUID>();
+ return buildStreamingPaginationResponse(refunds,
+ new Function<Refund, RefundJson>() {
+ @Override
+ public RefundJson apply(final Refund refund) {
+ UUID kbAccountId = null;
+ if (!AuditLevel.NONE.equals(auditMode.getLevel()) && paymentIdAccountIdMappings.get(refund.getPaymentId()) == null) {
+ try {
+ kbAccountId = paymentApi.getPayment(refund.getPaymentId(), false, tenantContext).getAccountId();
+ paymentIdAccountIdMappings.put(refund.getPaymentId(), kbAccountId);
+ } catch (final PaymentApiException e) {
+ log.warn("Unable to retrieve payment for id " + refund.getPaymentId());
+ }
+ }
+
+ // Cache audit logs per account
+ if (accountsAuditLogs.get().get(kbAccountId) == null) {
+ accountsAuditLogs.get().put(kbAccountId, auditUserApi.getAccountAuditLogs(kbAccountId, auditMode.getLevel(), tenantContext));
+ }
+
+ final List<AuditLog> auditLogs = accountsAuditLogs.get().get(kbAccountId) == null ? null : accountsAuditLogs.get().get(kbAccountId).getAuditLogsForRefund(refund.getId());
+ return new RefundJson(refund, null, auditLogs);
+ }
+ },
+ nextPageUri);
+ }
+
+ @GET
+ @Path("/" + SEARCH + "/{searchKey:" + ANYTHING_PATTERN + "}")
+ @Produces(APPLICATION_JSON)
+ public Response searchRefunds(@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_PLUGIN_NAME) final String pluginName,
+ @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException {
+ final TenantContext tenantContext = context.createContext(request);
+
+ // Search the plugin(s)
+ final Pagination<Refund> refunds;
+ if (Strings.isNullOrEmpty(pluginName)) {
+ refunds = paymentApi.searchRefunds(searchKey, offset, limit, tenantContext);
+ } else {
+ refunds = paymentApi.searchRefunds(searchKey, offset, limit, pluginName, tenantContext);
+ }
+
+ final URI nextPageUri = uriBuilder.nextPage(RefundResource.class, "searchRefunds", refunds.getNextOffset(), limit, ImmutableMap.<String, String>of("searchKey", searchKey,
+ QUERY_PAYMENT_METHOD_PLUGIN_NAME, Strings.nullToEmpty(pluginName),
+ QUERY_AUDIT, auditMode.toString()));
+
+ final AtomicReference<Map<UUID, AccountAuditLogs>> accountsAuditLogs = new AtomicReference<Map<UUID, AccountAuditLogs>>(new HashMap<UUID, AccountAuditLogs>());
+ final Map<UUID, UUID> paymentIdAccountIdMappings = new HashMap<UUID, UUID>();
+ return buildStreamingPaginationResponse(refunds,
+ new Function<Refund, RefundJson>() {
+ @Override
+ public RefundJson apply(final Refund refund) {
+ UUID kbAccountId = null;
+ if (!AuditLevel.NONE.equals(auditMode.getLevel()) && paymentIdAccountIdMappings.get(refund.getPaymentId()) == null) {
+ try {
+ kbAccountId = paymentApi.getPayment(refund.getPaymentId(), false, tenantContext).getAccountId();
+ paymentIdAccountIdMappings.put(refund.getPaymentId(), kbAccountId);
+ } catch (final PaymentApiException e) {
+ log.warn("Unable to retrieve payment for id " + refund.getPaymentId());
+ }
+ }
+
+ // Cache audit logs per account
+ if (accountsAuditLogs.get().get(kbAccountId) == null) {
+ accountsAuditLogs.get().put(kbAccountId, auditUserApi.getAccountAuditLogs(kbAccountId, auditMode.getLevel(), tenantContext));
+ }
+
+ final List<AuditLog> auditLogs = accountsAuditLogs.get().get(kbAccountId) == null ? null : accountsAuditLogs.get().get(kbAccountId).getAuditLogsForRefund(refund.getId());
+ return new RefundJson(refund, null, auditLogs);
+ }
+ },
+ nextPageUri);
}
@Override
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/TagDefinitionResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/TagDefinitionResource.java
new file mode 100644
index 0000000..bc7202c
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/TagDefinitionResource.java
@@ -0,0 +1,123 @@
+/*
+ * 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.jaxrs.resources;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import com.ning.billing.ObjectType;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.clock.Clock;
+import com.ning.billing.jaxrs.json.TagDefinitionJson;
+import com.ning.billing.jaxrs.util.Context;
+import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
+import com.ning.billing.util.api.AuditUserApi;
+import com.ning.billing.util.api.CustomFieldUserApi;
+import com.ning.billing.util.api.TagDefinitionApiException;
+import com.ning.billing.util.api.TagUserApi;
+import com.ning.billing.util.tag.TagDefinition;
+
+import com.google.common.base.Preconditions;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+
+@Singleton
+@Path(JaxrsResource.TAG_DEFINITIONS_PATH)
+public class TagDefinitionResource extends JaxRsResourceBase {
+
+ @Inject
+ public TagDefinitionResource(final JaxrsUriBuilder uriBuilder,
+ final TagUserApi tagUserApi,
+ final CustomFieldUserApi customFieldUserApi,
+ final AuditUserApi auditUserApi,
+ final AccountUserApi accountUserApi,
+ final Clock clock,
+ final Context context) {
+ super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, clock, context);
+ }
+
+ @GET
+ @Produces(APPLICATION_JSON)
+ public Response getTagDefinitions(@javax.ws.rs.core.Context final HttpServletRequest request) {
+ final List<TagDefinition> tagDefinitions = tagUserApi.getTagDefinitions(context.createContext(request));
+
+ final List<TagDefinitionJson> result = new LinkedList<TagDefinitionJson>();
+ for (final TagDefinition cur : tagDefinitions) {
+ result.add(new TagDefinitionJson(cur));
+ }
+
+ return Response.status(Status.OK).entity(result).build();
+ }
+
+ @GET
+ @Path("/{tagDefinitionId:" + UUID_PATTERN + "}")
+ @Produces(APPLICATION_JSON)
+ public Response getTagDefinition(@PathParam("tagDefinitionId") final String tagDefId,
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws TagDefinitionApiException {
+ final TagDefinition tagDef = tagUserApi.getTagDefinition(UUID.fromString(tagDefId), context.createContext(request));
+ final TagDefinitionJson json = new TagDefinitionJson(tagDef);
+ return Response.status(Status.OK).entity(json).build();
+ }
+
+ @POST
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ public Response createTagDefinition(final TagDefinitionJson json,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment,
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws TagDefinitionApiException {
+ // Checked as the database layer as well, but bail early and return 400 instead of 500
+ Preconditions.checkNotNull(json.getName(), String.format("TagDefinition name needs to be set"));
+ Preconditions.checkNotNull(json.getDescription(), String.format("TagDefinition description needs to be set"));
+
+ final TagDefinition createdTagDef = tagUserApi.createTagDefinition(json.getName(), json.getDescription(), context.createContext(createdBy, reason, comment, request));
+ return uriBuilder.buildResponse(TagDefinitionResource.class, "getTagDefinition", createdTagDef.getId());
+ }
+
+ @DELETE
+ @Path("/{tagDefinitionId:" + UUID_PATTERN + "}")
+ @Produces(APPLICATION_JSON)
+ public Response deleteTagDefinition(@PathParam("tagDefinitionId") final String tagDefId,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment,
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws TagDefinitionApiException {
+ tagUserApi.deleteTagDefinition(UUID.fromString(tagDefId), context.createContext(createdBy, reason, comment, request));
+ return Response.status(Status.NO_CONTENT).build();
+ }
+
+ @Override
+ protected ObjectType getObjectType() {
+ return ObjectType.TAG_DEFINITION;
+ }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/TagResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/TagResource.java
index 1bbff05..0734a08 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/TagResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/TagResource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2010-2014 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
@@ -16,42 +16,46 @@
package com.ning.billing.jaxrs.resources;
-import java.util.LinkedList;
+import java.net.URI;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
-import javax.ws.rs.HeaderParam;
-import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
-import javax.ws.rs.core.Response.Status;
import com.ning.billing.ObjectType;
import com.ning.billing.account.api.AccountUserApi;
import com.ning.billing.clock.Clock;
-import com.ning.billing.jaxrs.json.TagDefinitionJson;
+import com.ning.billing.jaxrs.json.TagJson;
import com.ning.billing.jaxrs.util.Context;
import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
import com.ning.billing.util.api.AuditUserApi;
import com.ning.billing.util.api.CustomFieldUserApi;
-import com.ning.billing.util.api.TagDefinitionApiException;
+import com.ning.billing.util.api.TagApiException;
import com.ning.billing.util.api.TagUserApi;
+import com.ning.billing.util.audit.AuditLog;
+import com.ning.billing.util.callcontext.TenantContext;
+import com.ning.billing.util.entity.Pagination;
+import com.ning.billing.util.tag.Tag;
import com.ning.billing.util.tag.TagDefinition;
-import com.google.common.base.Preconditions;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableMap;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
@Singleton
-@Path(JaxrsResource.TAG_DEFINITIONS_PATH)
+@Path(JaxrsResource.TAGS_PATH)
public class TagResource extends JaxRsResourceBase {
@Inject
@@ -66,58 +70,62 @@ public class TagResource extends JaxRsResourceBase {
}
@GET
+ @Path("/" + PAGINATION)
@Produces(APPLICATION_JSON)
- public Response getTagDefinitions(@javax.ws.rs.core.Context final HttpServletRequest request) {
- final List<TagDefinition> tagDefinitions = tagUserApi.getTagDefinitions(context.createContext(request));
-
- final List<TagDefinitionJson> result = new LinkedList<TagDefinitionJson>();
- for (final TagDefinition cur : tagDefinitions) {
- result.add(new TagDefinitionJson(cur));
+ public Response getTags(@QueryParam(QUERY_SEARCH_OFFSET) @DefaultValue("0") final Long offset,
+ @QueryParam(QUERY_SEARCH_LIMIT) @DefaultValue("100") final Long limit,
+ @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws TagApiException {
+ final TenantContext tenantContext = context.createContext(request);
+ final Pagination<Tag> tags = tagUserApi.getTags(offset, limit, tenantContext);
+ final URI nextPageUri = uriBuilder.nextPage(TagResource.class, "getTags", tags.getNextOffset(), limit, ImmutableMap.<String, String>of(QUERY_AUDIT, auditMode.toString()));
+
+ final Map<UUID, TagDefinition> tagDefinitionsCache = new HashMap<UUID, TagDefinition>();
+ for (final TagDefinition tagDefinition : tagUserApi.getTagDefinitions(tenantContext)) {
+ tagDefinitionsCache.put(tagDefinition.getId(), tagDefinition);
}
- return Response.status(Status.OK).entity(result).build();
+ return buildStreamingPaginationResponse(tags,
+ new Function<Tag, TagJson>() {
+ @Override
+ public TagJson apply(final Tag tag) {
+ final TagDefinition tagDefinition = tagDefinitionsCache.get(tag.getTagDefinitionId());
+
+ // TODO Really slow - we should instead try to figure out the account id
+ final List<AuditLog> auditLogs = auditUserApi.getAuditLogs(tag.getId(), ObjectType.TAG, auditMode.getLevel(), tenantContext);
+ return new TagJson(tag, tagDefinition, auditLogs);
+ }
+ },
+ nextPageUri);
}
@GET
- @Path("/{tagDefinitionId:" + UUID_PATTERN + "}")
- @Produces(APPLICATION_JSON)
- public Response getTagDefinition(@PathParam("tagDefinitionId") final String tagDefId,
- @javax.ws.rs.core.Context final HttpServletRequest request) throws TagDefinitionApiException {
- final TagDefinition tagDef = tagUserApi.getTagDefinition(UUID.fromString(tagDefId), context.createContext(request));
- final TagDefinitionJson json = new TagDefinitionJson(tagDef);
- return Response.status(Status.OK).entity(json).build();
- }
-
- @POST
- @Consumes(APPLICATION_JSON)
+ @Path("/" + SEARCH + "/{searchKey:" + ANYTHING_PATTERN + "}")
@Produces(APPLICATION_JSON)
- public Response createTagDefinition(final TagDefinitionJson json,
- @HeaderParam(HDR_CREATED_BY) final String createdBy,
- @HeaderParam(HDR_REASON) final String reason,
- @HeaderParam(HDR_COMMENT) final String comment,
- @javax.ws.rs.core.Context final HttpServletRequest request) throws TagDefinitionApiException {
- // Checked as the database layer as well, but bail early and return 400 instead of 500
- Preconditions.checkNotNull(json.getName(), String.format("TagDefinition name needs to be set"));
- Preconditions.checkNotNull(json.getDescription(), String.format("TagDefinition description needs to be set"));
-
- final TagDefinition createdTagDef = tagUserApi.createTagDefinition(json.getName(), json.getDescription(), context.createContext(createdBy, reason, comment, request));
- return uriBuilder.buildResponse(TagResource.class, "getTagDefinition", createdTagDef.getId());
- }
-
- @DELETE
- @Path("/{tagDefinitionId:" + UUID_PATTERN + "}")
- @Produces(APPLICATION_JSON)
- public Response deleteTagDefinition(@PathParam("tagDefinitionId") final String tagDefId,
- @HeaderParam(HDR_CREATED_BY) final String createdBy,
- @HeaderParam(HDR_REASON) final String reason,
- @HeaderParam(HDR_COMMENT) final String comment,
- @javax.ws.rs.core.Context final HttpServletRequest request) throws TagDefinitionApiException {
- tagUserApi.deleteTagDefinition(UUID.fromString(tagDefId), context.createContext(createdBy, reason, comment, request));
- return Response.status(Status.NO_CONTENT).build();
- }
-
- @Override
- protected ObjectType getObjectType() {
- return ObjectType.TAG_DEFINITION;
+ public Response searchTags(@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_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws TagApiException {
+ final TenantContext tenantContext = context.createContext(request);
+ final Pagination<Tag> tags = tagUserApi.searchTags(searchKey, offset, limit, tenantContext);
+ final URI nextPageUri = uriBuilder.nextPage(TagResource.class, "searchTags", tags.getNextOffset(), limit, ImmutableMap.<String, String>of("searchKey", searchKey,
+ QUERY_AUDIT, auditMode.toString()));
+ final Map<UUID, TagDefinition> tagDefinitionsCache = new HashMap<UUID, TagDefinition>();
+ for (final TagDefinition tagDefinition : tagUserApi.getTagDefinitions(tenantContext)) {
+ tagDefinitionsCache.put(tagDefinition.getId(), tagDefinition);
+ }
+ return buildStreamingPaginationResponse(tags,
+ new Function<Tag, TagJson>() {
+ @Override
+ public TagJson apply(final Tag tag) {
+ final TagDefinition tagDefinition = tagDefinitionsCache.get(tag.getTagDefinitionId());
+
+ // TODO Really slow - we should instead try to figure out the account id
+ final List<AuditLog> auditLogs = auditUserApi.getAuditLogs(tag.getId(), ObjectType.TAG, auditMode.getLevel(), tenantContext);
+ return new TagJson(tag, tagDefinition, auditLogs);
+ }
+ },
+ nextPageUri);
}
}
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 6377b2d..a27b34e 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
@@ -122,6 +122,16 @@ public class JRubyPaymentPlugin extends JRubyPlugin implements PaymentPluginApi
}
@Override
+ public Pagination<RefundInfoPlugin> searchRefunds(final String searchKey, final Long offset, final Long limit, final TenantContext tenantContext) throws PaymentPluginApiException {
+ return callWithRuntimeAndChecking(new PluginCallback(VALIDATION_PLUGIN_TYPE.PAYMENT) {
+ @Override
+ public Pagination<RefundInfoPlugin> doCall(final Ruby runtime) throws PaymentPluginApiException {
+ return ((PaymentPluginApi) pluginInstance).searchRefunds(searchKey, offset, limit, tenantContext);
+ }
+ });
+ }
+
+ @Override
public void addPaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final PaymentMethodPlugin paymentMethodProps, final boolean setDefault, final CallContext context) throws PaymentPluginApiException {
callWithRuntimeAndChecking(new PluginCallback(VALIDATION_PLUGIN_TYPE.PAYMENT) {
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 3396d2c..97552f8 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
@@ -149,6 +149,36 @@ public class TestPaymentPluginApi implements PaymentPluginApi {
}
@Override
+ public Pagination<RefundInfoPlugin> searchRefunds(final String searchKey, final Long offset, final Long limit, final TenantContext tenantContext) throws PaymentPluginApiException {
+ return new Pagination<RefundInfoPlugin>() {
+ @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<RefundInfoPlugin> iterator() {
+ return null;
+ }
+ };
+ }
+
+ @Override
public void addPaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final PaymentMethodPlugin paymentMethodProps, final boolean setDefault, final CallContext context) throws PaymentPluginApiException {
}
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 854aa07..fe376d1 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
@@ -193,10 +193,13 @@ public class TestPaymentPluginApi implements PaymentPluginApiWithTestControl {
@Override
public RefundInfoPlugin processRefund(final UUID accountId, final UUID kbPaymentId, final BigDecimal refundAmount, final Currency currency, final CallContext context) throws PaymentPluginApiException {
-
- final BigDecimal someAmount = new BigDecimal("12.45");
return withRuntimeCheckForExceptions(new RefundInfoPlugin() {
@Override
+ public UUID getKbPaymentId() {
+ return kbPaymentId;
+ }
+
+ @Override
public BigDecimal getAmount() {
return null;
}
@@ -232,7 +235,12 @@ public class TestPaymentPluginApi implements PaymentPluginApiWithTestControl {
}
@Override
- public String getReferenceId() {
+ public String getFirstRefundReferenceId() {
+ return null;
+ }
+
+ @Override
+ public String getSecondRefundReferenceId() {
return null;
}
});
@@ -244,6 +252,36 @@ public class TestPaymentPluginApi implements PaymentPluginApiWithTestControl {
}
@Override
+ public Pagination<RefundInfoPlugin> searchRefunds(final String searchKey, final Long offset, final Long limit, final TenantContext tenantContext) throws PaymentPluginApiException {
+ return new Pagination<RefundInfoPlugin>() {
+ @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<RefundInfoPlugin> iterator() {
+ return null;
+ }
+ };
+ }
+
+ @Override
public void addPaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final PaymentMethodPlugin paymentMethodProps, final boolean setDefault, final CallContext context) throws PaymentPluginApiException {
}
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 e1777d2..3f00e66 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
@@ -116,6 +116,26 @@ public class DefaultPaymentApi implements PaymentApi {
}
@Override
+ public Pagination<Refund> getRefunds(final Long offset, final Long limit, final TenantContext context) {
+ return refundProcessor.getRefunds(offset, limit, context, internalCallContextFactory.createInternalTenantContext(context));
+ }
+
+ @Override
+ public Pagination<Refund> getRefunds(final Long offset, final Long limit, final String pluginName, final TenantContext tenantContext) throws PaymentApiException {
+ return refundProcessor.getRefunds(offset, limit, pluginName, tenantContext, internalCallContextFactory.createInternalTenantContext(tenantContext));
+ }
+
+ @Override
+ public Pagination<Refund> searchRefunds(final String searchKey, final Long offset, final Long limit, final TenantContext context) {
+ return refundProcessor.searchRefunds(searchKey, offset, limit, internalCallContextFactory.createInternalTenantContext(context));
+ }
+
+ @Override
+ public Pagination<Refund> searchRefunds(final String searchKey, final Long offset, final Long limit, final String pluginName, final TenantContext context) throws PaymentApiException {
+ return refundProcessor.searchRefunds(searchKey, offset, limit, pluginName, internalCallContextFactory.createInternalTenantContext(context));
+ }
+
+ @Override
public List<Payment> getInvoicePayments(final UUID invoiceId, final TenantContext context) {
return paymentProcessor.getInvoicePayments(invoiceId, internalCallContextFactory.createInternalTenantContext(context));
}
diff --git a/payment/src/main/java/com/ning/billing/payment/api/DefaultRefund.java b/payment/src/main/java/com/ning/billing/payment/api/DefaultRefund.java
index 9349504..b708a01 100644
--- a/payment/src/main/java/com/ning/billing/payment/api/DefaultRefund.java
+++ b/payment/src/main/java/com/ning/billing/payment/api/DefaultRefund.java
@@ -25,6 +25,7 @@ import org.joda.time.DateTime;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.entity.EntityBase;
+import com.ning.billing.payment.dao.RefundModelDao;
import com.ning.billing.payment.plugin.api.RefundInfoPlugin;
public class DefaultRefund extends EntityBase implements Refund {
@@ -35,10 +36,12 @@ public class DefaultRefund extends EntityBase implements Refund {
private final boolean isAdjusted;
private final DateTime effectiveDate;
private final RefundStatus refundStatus;
+ private final RefundInfoPlugin refundInfoPlugin;
public DefaultRefund(final UUID id, @Nullable final DateTime createdDate, @Nullable final DateTime updatedDate,
final UUID paymentId, final BigDecimal amount,
- final Currency currency, final boolean isAdjusted, final DateTime effectiveDate, final RefundStatus refundStatus) {
+ final Currency currency, final boolean isAdjusted, final DateTime effectiveDate,
+ final RefundStatus refundStatus, final RefundInfoPlugin refundInfoPlugin) {
super(id, createdDate, updatedDate);
this.paymentId = paymentId;
this.amount = amount;
@@ -46,6 +49,19 @@ public class DefaultRefund extends EntityBase implements Refund {
this.isAdjusted = isAdjusted;
this.effectiveDate = effectiveDate;
this.refundStatus = refundStatus;
+ this.refundInfoPlugin = refundInfoPlugin;
+ }
+
+ public DefaultRefund(final RefundModelDao refundModelDao, @Nullable final RefundInfoPlugin refundInfoPlugin) {
+ this(refundModelDao.getId(), refundModelDao.getCreatedDate(), refundModelDao.getUpdatedDate(),
+ refundModelDao.getPaymentId(), refundModelDao.getAmount(), refundModelDao.getCurrency(),
+ refundModelDao.isAdjusted(), refundModelDao.getCreatedDate(), refundModelDao.getRefundStatus(), refundInfoPlugin);
+ }
+
+ public DefaultRefund(final UUID id, @Nullable final DateTime createdDate, @Nullable final DateTime updatedDate,
+ final UUID paymentId, final BigDecimal amount,
+ final Currency currency, final boolean isAdjusted, final DateTime effectiveDate, final RefundStatus refundStatus) {
+ this(id, createdDate, updatedDate, paymentId, amount, currency, isAdjusted, effectiveDate, refundStatus, null);
}
@Override
@@ -79,21 +95,20 @@ public class DefaultRefund extends EntityBase implements Refund {
}
@Override
- public RefundInfoPlugin getPluginDetail() {
- // TODO not implemented
- return null;
+ public RefundInfoPlugin getRefundInfoPlugin() {
+ return refundInfoPlugin;
}
@Override
public String toString() {
- final StringBuilder sb = new StringBuilder();
- sb.append("DefaultRefund");
- sb.append("{paymentId=").append(paymentId);
+ final StringBuilder sb = new StringBuilder("DefaultRefund{");
+ sb.append("paymentId=").append(paymentId);
sb.append(", amount=").append(amount);
sb.append(", currency=").append(currency);
sb.append(", isAdjusted=").append(isAdjusted);
- sb.append(", status=").append(refundStatus);
sb.append(", effectiveDate=").append(effectiveDate);
+ sb.append(", refundStatus=").append(refundStatus);
+ sb.append(", refundInfoPlugin=").append(refundInfoPlugin);
sb.append('}');
return sb.toString();
}
@@ -106,39 +121,47 @@ public class DefaultRefund extends EntityBase implements Refund {
if (o == null || getClass() != o.getClass()) {
return false;
}
+ if (!super.equals(o)) {
+ return false;
+ }
final DefaultRefund that = (DefaultRefund) o;
if (isAdjusted != that.isAdjusted) {
return false;
}
- if (amount != null ? !amount.equals(that.amount) : that.amount != null) {
- return false;
- }
- if (effectiveDate != null ? !effectiveDate.equals(that.effectiveDate) : that.effectiveDate != null) {
+ if (amount != null ? amount.compareTo(that.amount) != 0 : that.amount != null) {
return false;
}
if (currency != that.currency) {
return false;
}
- if (refundStatus != that.refundStatus) {
+ if (effectiveDate != null ? effectiveDate.compareTo(that.effectiveDate) != 0 : that.effectiveDate != null) {
return false;
}
if (paymentId != null ? !paymentId.equals(that.paymentId) : that.paymentId != null) {
return false;
}
+ if (refundInfoPlugin != null ? !refundInfoPlugin.equals(that.refundInfoPlugin) : that.refundInfoPlugin != null) {
+ return false;
+ }
+ if (refundStatus != that.refundStatus) {
+ return false;
+ }
return true;
}
@Override
public int hashCode() {
- int result = paymentId != null ? paymentId.hashCode() : 0;
+ int result = super.hashCode();
+ result = 31 * result + (paymentId != null ? paymentId.hashCode() : 0);
result = 31 * result + (amount != null ? amount.hashCode() : 0);
result = 31 * result + (currency != null ? currency.hashCode() : 0);
- result = 31 * result + (refundStatus != null ? refundStatus.hashCode() : 0);
result = 31 * result + (isAdjusted ? 1 : 0);
result = 31 * result + (effectiveDate != null ? effectiveDate.hashCode() : 0);
+ result = 31 * result + (refundStatus != null ? refundStatus.hashCode() : 0);
+ result = 31 * result + (refundInfoPlugin != null ? refundInfoPlugin.hashCode() : 0);
return result;
}
}
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 4117e03..0e65f04 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
@@ -18,7 +18,6 @@ package com.ning.billing.payment.core;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
@@ -55,18 +54,19 @@ import com.ning.billing.payment.provider.ExternalPaymentProviderPlugin;
import com.ning.billing.tag.TagInternalApi;
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.ning.billing.util.entity.dao.DefaultPaginationHelper.EntityPaginationBuilder;
+import com.ning.billing.util.entity.dao.DefaultPaginationHelper.SourcePaginationBuilder;
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;
import static com.ning.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
+import static com.ning.billing.util.entity.dao.DefaultPaginationHelper.getEntityPagination;
+import static com.ning.billing.util.entity.dao.DefaultPaginationHelper.getEntityPaginationFromPlugins;
public class PaymentMethodProcessor extends ProcessorBase {
@@ -156,134 +156,89 @@ public class PaymentMethodProcessor extends ProcessorBase {
}
public Pagination<PaymentMethod> getPaymentMethods(final Long offset, final Long limit, final TenantContext tenantContext, 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>();
- Long totalNbRecords = 0L;
- Long maxNbRecords = 0L;
-
- // Search in all plugins (we treat the full set of results as a union with respect to offset/limit)
- boolean firstSearch = true;
- for (final String pluginName : getAvailablePlugins()) {
- try {
- final Pagination<PaymentMethod> paymentMethods;
- if (allResults.size() >= limit) {
- // We have enough results, we just keep going (limit 1) to get the stats
- paymentMethods = getPaymentMethods(firstSearch ? offset : 0L, 1L, pluginName, tenantContext, internalTenantContext);
- // Required to close database connections
- ImmutableList.<PaymentMethod>copyOf(paymentMethods);
- } else {
- paymentMethods = getPaymentMethods(firstSearch ? offset : 0L, limit - allResults.size(), pluginName, tenantContext, internalTenantContext);
- allResults.addAll(ImmutableList.<PaymentMethod>copyOf(paymentMethods));
- }
- firstSearch = false;
- totalNbRecords += paymentMethods.getTotalNbRecords();
- maxNbRecords += paymentMethods.getMaxNbRecords();
- } catch (PaymentApiException e) {
- log.warn("Error while searching plugin " + pluginName, e);
- // Non-fatal, continue to search other plugins
- }
- }
-
- return new DefaultPagination<PaymentMethod>(offset, limit, totalNbRecords, maxNbRecords, allResults.iterator());
+ return getEntityPaginationFromPlugins(getAvailablePlugins(),
+ offset,
+ limit,
+ new EntityPaginationBuilder<PaymentMethod, PaymentApiException>() {
+ @Override
+ public Pagination<PaymentMethod> build(final Long offset, final Long limit, final String pluginName) throws PaymentApiException {
+ return getPaymentMethods(offset, limit, pluginName, tenantContext, internalTenantContext);
+ }
+ });
}
public Pagination<PaymentMethod> getPaymentMethods(final Long offset, final Long limit, final String pluginName, final TenantContext tenantContext, final InternalTenantContext internalTenantContext) throws PaymentApiException {
final PaymentPluginApi pluginApi = getPaymentPluginApi(pluginName);
- // Find all payment methods for all accounts
- final Pagination<PaymentMethodModelDao> paymentMethodModelDaos = paymentDao.getPaymentMethods(pluginName, offset, limit, internalTenantContext);
-
- return new DefaultPagination<PaymentMethod>(paymentMethodModelDaos,
- limit,
- Iterators.<PaymentMethod>filter(Iterators.<PaymentMethodModelDao, PaymentMethod>transform(paymentMethodModelDaos.iterator(),
- new Function<PaymentMethodModelDao, PaymentMethod>() {
- @Override
- public PaymentMethod apply(final PaymentMethodModelDao paymentMethodModelDao) {
- PaymentMethodPlugin paymentMethodPlugin = null;
- try {
- paymentMethodPlugin = pluginApi.getPaymentMethodDetail(paymentMethodModelDao.getAccountId(), paymentMethodModelDao.getId(), tenantContext);
- if (paymentMethodPlugin.getKbPaymentMethodId() == null) {
- // Garbage from the plugin?
- log.debug("Plugin {} returned a payment method without a kbPaymentMethodId", pluginName);
- paymentMethodPlugin = null;
- }
- } catch (PaymentPluginApiException e) {
- log.warn("Unable to find payment method id " + paymentMethodModelDao.getId() + " in plugin " + pluginName);
- }
-
- return new DefaultPaymentMethod(paymentMethodModelDao, paymentMethodPlugin);
- }
- }),
- Predicates.<PaymentMethod>notNull()));
+ return getEntityPagination(limit,
+ new SourcePaginationBuilder<PaymentMethodModelDao, PaymentApiException>() {
+ @Override
+ public Pagination<PaymentMethodModelDao> build() {
+ // Find all payment methods for all accounts
+ return paymentDao.getPaymentMethods(pluginName, offset, limit, internalTenantContext);
+ }
+ },
+ new Function<PaymentMethodModelDao, PaymentMethod>() {
+ @Override
+ public PaymentMethod apply(final PaymentMethodModelDao paymentMethodModelDao) {
+ PaymentMethodPlugin paymentMethodPlugin = null;
+ try {
+ paymentMethodPlugin = pluginApi.getPaymentMethodDetail(paymentMethodModelDao.getAccountId(), paymentMethodModelDao.getId(), tenantContext);
+ } catch (PaymentPluginApiException e) {
+ log.warn("Unable to find payment method id " + paymentMethodModelDao.getId() + " in plugin " + pluginName);
+ }
+
+ return new DefaultPaymentMethod(paymentMethodModelDao, paymentMethodPlugin);
+ }
+ }
+ );
}
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>();
- Long totalNbRecords = 0L;
- Long maxNbRecords = 0L;
-
- // Search in all plugins (we treat the full set of results as a union with respect to offset/limit)
- boolean firstSearch = true;
- for (final String pluginName : getAvailablePlugins()) {
- try {
- final Pagination<PaymentMethod> paymentMethods;
- if (allResults.size() >= limit) {
- // We have enough results, we just keep going (limit 1) to get the stats
- paymentMethods = searchPaymentMethods(searchKey, firstSearch ? offset : 0L, 1L, pluginName, internalTenantContext);
- // Required to close database connections
- ImmutableList.<PaymentMethod>copyOf(paymentMethods);
- } else {
- paymentMethods = searchPaymentMethods(searchKey, firstSearch ? offset : 0L, limit - allResults.size(), pluginName, internalTenantContext);
- allResults.addAll(ImmutableList.<PaymentMethod>copyOf(paymentMethods));
- }
- firstSearch = false;
- totalNbRecords += paymentMethods.getTotalNbRecords();
- maxNbRecords += paymentMethods.getMaxNbRecords();
- } catch (PaymentApiException e) {
- log.warn("Error while searching plugin " + pluginName, e);
- // Non-fatal, continue to search other plugins
- }
- }
-
- return new DefaultPagination<PaymentMethod>(offset, limit, totalNbRecords, maxNbRecords, allResults.iterator());
+ return getEntityPaginationFromPlugins(getAvailablePlugins(),
+ offset,
+ limit,
+ new EntityPaginationBuilder<PaymentMethod, PaymentApiException>() {
+ @Override
+ public Pagination<PaymentMethod> build(final Long offset, final Long limit, final String pluginName) throws PaymentApiException {
+ return searchPaymentMethods(searchKey, offset, limit, pluginName, internalTenantContext);
+ }
+ });
}
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 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()));
+ return getEntityPagination(limit,
+ new SourcePaginationBuilder<PaymentMethodPlugin, PaymentApiException>() {
+ @Override
+ public Pagination<PaymentMethodPlugin> build() throws PaymentApiException {
+ try {
+ return pluginApi.searchPaymentMethods(searchKey, offset, limit, buildTenantContext(internalTenantContext));
+ } catch (final PaymentPluginApiException e) {
+ throw new PaymentApiException(e, ErrorCode.PAYMENT_PLUGIN_SEARCH_PAYMENT_METHODS, pluginName, searchKey);
+ }
+ }
+ },
+ 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);
+ }
+ }
+ );
}
public PaymentMethod getExternalPaymentMethod(final Account account, final InternalTenantContext context) throws PaymentApiException {
diff --git a/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java b/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java
index 567a8ca..eba9931 100644
--- a/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java
+++ b/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java
@@ -72,18 +72,18 @@ import com.ning.billing.tag.TagInternalApi;
import com.ning.billing.util.callcontext.TenantContext;
import com.ning.billing.util.config.PaymentConfig;
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.DefaultPaginationHelper.EntityPaginationBuilder;
+import com.ning.billing.util.entity.dao.DefaultPaginationHelper.SourcePaginationBuilder;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
-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.name.Named;
import static com.ning.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
+import static com.ning.billing.util.entity.dao.DefaultPaginationHelper.getEntityPagination;
+import static com.ning.billing.util.entity.dao.DefaultPaginationHelper.getEntityPaginationFromPlugins;
public class PaymentProcessor extends ProcessorBase {
@@ -149,134 +149,89 @@ public class PaymentProcessor extends ProcessorBase {
}
public Pagination<Payment> getPayments(final Long offset, final Long limit, final TenantContext tenantContext, 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<Payment> allResults = new LinkedList<Payment>();
- Long totalNbRecords = 0L;
- Long maxNbRecords = 0L;
-
- // Search in all plugins (we treat the full set of results as a union with respect to offset/limit)
- boolean firstSearch = true;
- for (final String pluginName : getAvailablePlugins()) {
- try {
- final Pagination<Payment> payments;
- if (allResults.size() >= limit) {
- // We have enough results, we just keep going (limit 1) to get the stats
- payments = getPayments(firstSearch ? offset : 0L, 1L, pluginName, tenantContext, internalTenantContext);
- // Required to close database connections
- ImmutableList.<Payment>copyOf(payments);
- } else {
- payments = getPayments(firstSearch ? offset : 0L, limit - allResults.size(), pluginName, tenantContext, internalTenantContext);
- allResults.addAll(ImmutableList.<Payment>copyOf(payments));
- }
- firstSearch = false;
- totalNbRecords += payments.getTotalNbRecords();
- maxNbRecords += payments.getMaxNbRecords();
- } catch (final PaymentApiException e) {
- log.warn("Error while searching plugin " + pluginName, e);
- // Non-fatal, continue to search other plugins
- }
- }
-
- return new DefaultPagination<Payment>(offset, limit, totalNbRecords, maxNbRecords, allResults.iterator());
+ return getEntityPaginationFromPlugins(getAvailablePlugins(),
+ offset,
+ limit,
+ new EntityPaginationBuilder<Payment, PaymentApiException>() {
+ @Override
+ public Pagination<Payment> build(final Long offset, final Long limit, final String pluginName) throws PaymentApiException {
+ return getPayments(offset, limit, pluginName, tenantContext, internalTenantContext);
+ }
+ });
}
public Pagination<Payment> getPayments(final Long offset, final Long limit, final String pluginName, final TenantContext tenantContext, final InternalTenantContext internalTenantContext) throws PaymentApiException {
final PaymentPluginApi pluginApi = getPaymentPluginApi(pluginName);
- // Find all payments for all accounts
- final Pagination<PaymentModelDao> paymentModelDaos = paymentDao.getPayments(pluginName, offset, limit, internalTenantContext);
-
- return new DefaultPagination<Payment>(paymentModelDaos,
- limit,
- Iterators.<Payment>filter(Iterators.<PaymentModelDao, Payment>transform(paymentModelDaos.iterator(),
- new Function<PaymentModelDao, Payment>() {
- @Override
- public Payment apply(final PaymentModelDao paymentModelDao) {
- PaymentInfoPlugin pluginInfo = null;
- try {
- pluginInfo = pluginApi.getPaymentInfo(paymentModelDao.getAccountId(), paymentModelDao.getId(), tenantContext);
- if (pluginInfo.getKbPaymentId() == null) {
- // Garbage from the plugin?
- log.debug("Plugin {} returned a payment without a kbPaymentId", pluginName);
- pluginInfo = null;
- }
- } catch (final PaymentPluginApiException e) {
- log.warn("Unable to find payment id " + paymentModelDao.getId() + " in plugin " + pluginName);
- }
-
- return fromPaymentModelDao(paymentModelDao, pluginInfo, internalTenantContext);
- }
- }),
- Predicates.<Payment>notNull()));
+ return getEntityPagination(limit,
+ new SourcePaginationBuilder<PaymentModelDao, PaymentApiException>() {
+ @Override
+ public Pagination<PaymentModelDao> build() {
+ // Find all payments for all accounts
+ return paymentDao.getPayments(pluginName, offset, limit, internalTenantContext);
+ }
+ },
+ new Function<PaymentModelDao, Payment>() {
+ @Override
+ public Payment apply(final PaymentModelDao paymentModelDao) {
+ PaymentInfoPlugin pluginInfo = null;
+ try {
+ pluginInfo = pluginApi.getPaymentInfo(paymentModelDao.getAccountId(), paymentModelDao.getId(), tenantContext);
+ } catch (final PaymentPluginApiException e) {
+ log.warn("Unable to find payment id " + paymentModelDao.getId() + " in plugin " + pluginName);
+ }
+
+ return fromPaymentModelDao(paymentModelDao, pluginInfo, internalTenantContext);
+ }
+ }
+ );
}
public Pagination<Payment> searchPayments(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<Payment> allResults = new LinkedList<Payment>();
- Long totalNbRecords = 0L;
- Long maxNbRecords = 0L;
-
- // Search in all plugins (we treat the full set of results as a union with respect to offset/limit)
- boolean firstSearch = true;
- for (final String pluginName : getAvailablePlugins()) {
- try {
- final Pagination<Payment> payments;
- if (allResults.size() >= limit) {
- // We have enough results, we just keep going (limit 1) to get the stats
- payments = searchPayments(searchKey, firstSearch ? offset : 0L, 1L, pluginName, internalTenantContext);
- // Required to close database connections
- ImmutableList.<Payment>copyOf(payments);
- } else {
- payments = searchPayments(searchKey, firstSearch ? offset : 0L, limit - allResults.size(), pluginName, internalTenantContext);
- allResults.addAll(ImmutableList.<Payment>copyOf(payments));
- }
- firstSearch = false;
- totalNbRecords += payments.getTotalNbRecords();
- maxNbRecords += payments.getMaxNbRecords();
- } catch (final PaymentApiException e) {
- log.warn("Error while searching plugin " + pluginName, e);
- // Non-fatal, continue to search other plugins
- }
- }
-
- return new DefaultPagination<Payment>(offset, limit, totalNbRecords, maxNbRecords, allResults.iterator());
+ return getEntityPaginationFromPlugins(getAvailablePlugins(),
+ offset,
+ limit,
+ new EntityPaginationBuilder<Payment, PaymentApiException>() {
+ @Override
+ public Pagination<Payment> build(final Long offset, final Long limit, final String pluginName) throws PaymentApiException {
+ return searchPayments(searchKey, offset, limit, pluginName, internalTenantContext);
+ }
+ });
}
public Pagination<Payment> searchPayments(final String searchKey, final Long offset, final Long limit, final String pluginName, final InternalTenantContext internalTenantContext) throws PaymentApiException {
final PaymentPluginApi pluginApi = getPaymentPluginApi(pluginName);
- final Pagination<PaymentInfoPlugin> payments;
- try {
- payments = pluginApi.searchPayments(searchKey, offset, limit, buildTenantContext(internalTenantContext));
- } catch (final PaymentPluginApiException e) {
- throw new PaymentApiException(e, ErrorCode.PAYMENT_PLUGIN_SEARCH_PAYMENTS, pluginName, searchKey);
- }
- return new DefaultPagination<Payment>(payments,
- limit,
- Iterators.<Payment>filter(Iterators.<PaymentInfoPlugin, Payment>transform(payments.iterator(),
- new Function<PaymentInfoPlugin, Payment>() {
- @Override
- public Payment apply(final PaymentInfoPlugin paymentInfoPlugin) {
- if (paymentInfoPlugin.getKbPaymentId() == null) {
- // Garbage from the plugin?
- log.debug("Plugin {} returned a payment without a kbPaymentId for searchKey {}", pluginName, searchKey);
- return null;
- }
-
- final PaymentModelDao model = paymentDao.getPayment(paymentInfoPlugin.getKbPaymentId(), internalTenantContext);
- if (model == null) {
- log.warn("Unable to find payment id " + paymentInfoPlugin.getKbPaymentId() + " present in plugin " + pluginName);
- return null;
- }
-
- return fromPaymentModelDao(model, paymentInfoPlugin, internalTenantContext);
- }
- }),
- Predicates.<Payment>notNull()));
+ return getEntityPagination(limit,
+ new SourcePaginationBuilder<PaymentInfoPlugin, PaymentApiException>() {
+ @Override
+ public Pagination<PaymentInfoPlugin> build() throws PaymentApiException {
+ try {
+ return pluginApi.searchPayments(searchKey, offset, limit, buildTenantContext(internalTenantContext));
+ } catch (final PaymentPluginApiException e) {
+ throw new PaymentApiException(e, ErrorCode.PAYMENT_PLUGIN_SEARCH_PAYMENTS, pluginName, searchKey);
+ }
+ }
+ },
+ new Function<PaymentInfoPlugin, Payment>() {
+ @Override
+ public Payment apply(final PaymentInfoPlugin paymentInfoPlugin) {
+ if (paymentInfoPlugin.getKbPaymentId() == null) {
+ // Garbage from the plugin?
+ log.debug("Plugin {} returned a payment without a kbPaymentId for searchKey {}", pluginName, searchKey);
+ return null;
+ }
+
+ final PaymentModelDao model = paymentDao.getPayment(paymentInfoPlugin.getKbPaymentId(), internalTenantContext);
+ if (model == null) {
+ log.warn("Unable to find payment id " + paymentInfoPlugin.getKbPaymentId() + " present in plugin " + pluginName);
+ return null;
+ }
+
+ return fromPaymentModelDao(model, paymentInfoPlugin, internalTenantContext);
+ }
+ }
+ );
}
public List<Payment> getInvoicePayments(final UUID invoiceId, final InternalTenantContext context) {
diff --git a/payment/src/main/java/com/ning/billing/payment/core/ProcessorBase.java b/payment/src/main/java/com/ning/billing/payment/core/ProcessorBase.java
index a68b447..5a128d6 100644
--- a/payment/src/main/java/com/ning/billing/payment/core/ProcessorBase.java
+++ b/payment/src/main/java/com/ning/billing/payment/core/ProcessorBase.java
@@ -30,28 +30,28 @@ import org.slf4j.LoggerFactory;
import com.ning.billing.ErrorCode;
import com.ning.billing.ObjectType;
import com.ning.billing.account.api.Account;
+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.commons.locker.GlobalLock;
import com.ning.billing.commons.locker.GlobalLocker;
import com.ning.billing.commons.locker.LockFailedException;
+import com.ning.billing.events.BusInternalEvent;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoiceInternalApi;
import com.ning.billing.osgi.api.OSGIServiceRegistration;
import com.ning.billing.payment.api.PaymentApiException;
import com.ning.billing.payment.dao.PaymentDao;
import com.ning.billing.payment.dao.PaymentMethodModelDao;
import com.ning.billing.payment.plugin.api.PaymentPluginApi;
+import com.ning.billing.tag.TagInternalApi;
import com.ning.billing.util.api.TagApiException;
-import com.ning.billing.callcontext.InternalCallContext;
-import com.ning.billing.callcontext.InternalTenantContext;
import com.ning.billing.util.callcontext.TenantContext;
import com.ning.billing.util.dao.NonEntityDao;
-import com.ning.billing.events.BusInternalEvent;
import com.ning.billing.util.globallocker.LockerType;
-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.tag.ControlTagType;
import com.ning.billing.util.tag.Tag;
diff --git a/payment/src/main/java/com/ning/billing/payment/core/RefundProcessor.java b/payment/src/main/java/com/ning/billing/payment/core/RefundProcessor.java
index b2a62b1..d5c0b27 100644
--- a/payment/src/main/java/com/ning/billing/payment/core/RefundProcessor.java
+++ b/payment/src/main/java/com/ning/billing/payment/core/RefundProcessor.java
@@ -20,11 +20,12 @@ import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
-import java.util.concurrent.TimeoutException;
import javax.annotation.Nullable;
import javax.inject.Inject;
@@ -47,31 +48,37 @@ import com.ning.billing.invoice.api.InvoiceItem;
import com.ning.billing.osgi.api.OSGIServiceRegistration;
import com.ning.billing.payment.api.DefaultRefund;
import com.ning.billing.payment.api.PaymentApiException;
-import com.ning.billing.payment.api.PaymentStatus;
import com.ning.billing.payment.api.Refund;
import com.ning.billing.payment.api.RefundStatus;
-import com.ning.billing.payment.dao.PaymentAttemptModelDao;
import com.ning.billing.payment.dao.PaymentDao;
import com.ning.billing.payment.dao.PaymentModelDao;
import com.ning.billing.payment.dao.RefundModelDao;
import com.ning.billing.payment.plugin.api.PaymentPluginApi;
import com.ning.billing.payment.plugin.api.PaymentPluginApiException;
import com.ning.billing.payment.plugin.api.RefundInfoPlugin;
+import com.ning.billing.payment.plugin.api.RefundPluginStatus;
import com.ning.billing.tag.TagInternalApi;
-import com.ning.billing.util.callcontext.CallContext;
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.dao.NonEntityDao;
+import com.ning.billing.util.entity.Pagination;
+import com.ning.billing.util.entity.dao.DefaultPaginationHelper.EntityPaginationBuilder;
+import com.ning.billing.util.entity.dao.DefaultPaginationHelper.SourcePaginationBuilder;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
import com.google.inject.name.Named;
import static com.ning.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
+import static com.ning.billing.util.entity.dao.DefaultPaginationHelper.getEntityPagination;
+import static com.ning.billing.util.entity.dao.DefaultPaginationHelper.getEntityPaginationFromPlugins;
public class RefundProcessor extends ProcessorBase {
@@ -151,7 +158,11 @@ public class RefundProcessor extends ProcessorBase {
default:
paymentDao.updateRefundStatus(refundInfo.getId(), RefundStatus.PLUGIN_ERRORED, refundAmount, account.getCurrency(), context);
throw new PaymentPluginApiException("Refund error for RefundInfo: " + refundInfo.toString(),
- String.format("Gateway error: %s, Gateway error code: %s, Reference id: %s", refundInfoPlugin.getGatewayError(), refundInfoPlugin.getGatewayErrorCode(), refundInfoPlugin.getReferenceId()));
+ String.format("Gateway error: %s, Gateway error code: %s, Reference ids: %s / %s",
+ refundInfoPlugin.getGatewayError(),
+ refundInfoPlugin.getGatewayErrorCode(),
+ refundInfoPlugin.getFirstRefundReferenceId(),
+ refundInfoPlugin.getSecondRefundReferenceId()));
}
} catch (PaymentPluginApiException e) {
throw new PaymentApiException(ErrorCode.PAYMENT_CREATE_REFUND, account.getId(), e.getErrorMessage());
@@ -162,7 +173,6 @@ public class RefundProcessor extends ProcessorBase {
});
}
-
public void notifyPendingRefundOfStateChanged(final Account account, final UUID refundId, final boolean isSuccess, final InternalCallContext context)
throws PaymentApiException {
@@ -231,23 +241,184 @@ public class RefundProcessor extends ProcessorBase {
throw new IllegalArgumentException("Unable to find invoice item for id " + itemId);
}
- public Refund getRefund(final UUID refundId, final boolean withPluginInfo /* not yet implemented */, final InternalTenantContext context)
- throws PaymentApiException {
+ public Refund getRefund(final UUID refundId, final boolean withPluginInfo, final InternalTenantContext context) throws PaymentApiException {
RefundModelDao result = paymentDao.getRefund(refundId, context);
if (result == null) {
throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_REFUND, refundId);
}
+
final List<RefundModelDao> filteredInput = filterUncompletedPluginRefund(Collections.singletonList(result));
- if (filteredInput.size() == 0) {
+ if (filteredInput.isEmpty()) {
throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_REFUND, refundId);
}
if (completePluginCompletedRefund(filteredInput, context)) {
result = paymentDao.getRefund(refundId, context);
}
- return new DefaultRefund(result.getId(), result.getCreatedDate(), result.getUpdatedDate(),
- result.getPaymentId(), result.getAmount(), result.getCurrency(),
- result.isAdjusted(), result.getCreatedDate(), result.getRefundStatus());
+
+ final Account account;
+ try {
+ account = accountInternalApi.getAccountById(result.getAccountId(), context);
+ } catch (final AccountApiException e) {
+ throw new PaymentApiException(e);
+ }
+
+ final PaymentPluginApi plugin = withPluginInfo ? getPaymentProviderPlugin(account, context) : null;
+ List<RefundInfoPlugin> refundInfoPlugins = ImmutableList.<RefundInfoPlugin>of();
+ if (plugin != null) {
+ try {
+ refundInfoPlugins = plugin.getRefundInfo(account.getId(), result.getPaymentId(), buildTenantContext(context));
+ } catch (final PaymentPluginApiException e) {
+ throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_GET_REFUND_INFO, refundId, e.toString());
+ }
+ }
+
+ return new DefaultRefund(result, findRefundInfoPlugin(result, refundInfoPlugins));
+ }
+
+ private RefundInfoPlugin findRefundInfoPlugin(final RefundModelDao refundModelDao, final List<RefundInfoPlugin> refundInfoPlugins) {
+ // We have a mapping 1:N for payment:refunds and a mapping 1:1 for RefundModelDao:RefundInfoPlugin.
+ // Unfortunately, we processing a refund, we don't tell the plugin about the refund id, so we need to do some heuristics
+ // to map a RefundInfoPlugin back to its RefundModelDao
+ // TODO This will break for multiple partial refunds of the same amount. Check the effective date won't help for same day partial refunds and checking effective datetime seems risky
+ return Iterables.<RefundInfoPlugin>tryFind(refundInfoPlugins,
+ new Predicate<RefundInfoPlugin>() {
+ @Override
+ public boolean apply(final RefundInfoPlugin refundInfoPlugin) {
+ return refundObjectsMatch(refundModelDao, refundInfoPlugin);
+ }
+ }).orNull();
+ }
+
+ private boolean refundObjectsMatch(final RefundModelDao refundModelDao, final RefundInfoPlugin refundInfoPlugin) {
+ return (refundInfoPlugin.getKbPaymentId() != null && refundModelDao.getPaymentId() != null && refundInfoPlugin.getKbPaymentId().equals(refundModelDao.getPaymentId())) &&
+ (refundInfoPlugin.getAmount() != null && refundModelDao.getProcessedAmount() != null && refundInfoPlugin.getAmount().compareTo(refundModelDao.getProcessedAmount()) == 0) &&
+ (refundInfoPlugin.getCurrency() != null && refundModelDao.getProcessedCurrency() != null && refundInfoPlugin.getCurrency().equals(refundModelDao.getProcessedCurrency())) &&
+ (
+ (refundInfoPlugin.getStatus().equals(RefundPluginStatus.PROCESSED) && refundModelDao.getRefundStatus().equals(RefundStatus.COMPLETED)) ||
+ (refundInfoPlugin.getStatus().equals(RefundPluginStatus.PENDING) && refundModelDao.getRefundStatus().equals(RefundStatus.PENDING))
+ );
+ }
+
+ public Pagination<Refund> getRefunds(final Long offset, final Long limit, final TenantContext tenantContext, final InternalTenantContext internalTenantContext) {
+ return getEntityPaginationFromPlugins(getAvailablePlugins(),
+ offset,
+ limit,
+ new EntityPaginationBuilder<Refund, PaymentApiException>() {
+ @Override
+ public Pagination<Refund> build(final Long offset, final Long limit, final String pluginName) throws PaymentApiException {
+ return getRefunds(offset, limit, pluginName, tenantContext, internalTenantContext);
+ }
+ });
+ }
+
+ public Pagination<Refund> getRefunds(final Long offset, final Long limit, final String pluginName, final TenantContext tenantContext, final InternalTenantContext internalTenantContext) throws PaymentApiException {
+ final PaymentPluginApi pluginApi = getPaymentPluginApi(pluginName);
+
+ return getEntityPagination(limit,
+ new SourcePaginationBuilder<RefundModelDao, PaymentApiException>() {
+ @Override
+ public Pagination<RefundModelDao> build() {
+ // Find all refunds for all accounts
+ return paymentDao.getRefunds(pluginName, offset, limit, internalTenantContext);
+ }
+ },
+ new Function<RefundModelDao, Refund>() {
+ @Override
+ public Refund apply(final RefundModelDao refundModelDao) {
+ List<RefundInfoPlugin> refundInfoPlugins = null;
+ try {
+ refundInfoPlugins = pluginApi.getRefundInfo(refundModelDao.getAccountId(), refundModelDao.getId(), tenantContext);
+ } catch (final PaymentPluginApiException e) {
+ log.warn("Unable to find refund id " + refundModelDao.getId() + " in plugin " + pluginName);
+ }
+
+ final RefundInfoPlugin refundInfoPlugin = findRefundInfoPlugin(refundModelDao, refundInfoPlugins);
+ return new DefaultRefund(refundModelDao, refundInfoPlugin);
+ }
+ }
+ );
+ }
+
+ public Pagination<Refund> searchRefunds(final String searchKey, final Long offset, final Long limit, final InternalTenantContext internalTenantContext) {
+ return getEntityPaginationFromPlugins(getAvailablePlugins(),
+ offset,
+ limit,
+ new EntityPaginationBuilder<Refund, PaymentApiException>() {
+ @Override
+ public Pagination<Refund> build(final Long offset, final Long limit, final String pluginName) throws PaymentApiException {
+ return searchRefunds(searchKey, offset, limit, pluginName, internalTenantContext);
+ }
+ });
+ }
+
+ public Pagination<Refund> searchRefunds(final String searchKey, final Long offset, final Long limit, final String pluginName, final InternalTenantContext internalTenantContext) throws PaymentApiException {
+ final PaymentPluginApi pluginApi = getPaymentPluginApi(pluginName);
+
+ final Map<UUID, List<RefundInfoPlugin>> refundsByPaymentId = new HashMap<UUID, List<RefundInfoPlugin>>();
+ final Map<UUID, List<RefundModelDao>> refundModelDaosByPaymentId = new HashMap<UUID, List<RefundModelDao>>();
+
+ return getEntityPagination(limit,
+ new SourcePaginationBuilder<RefundInfoPlugin, PaymentApiException>() {
+ @Override
+ public Pagination<RefundInfoPlugin> build() throws PaymentApiException {
+ final Pagination<RefundInfoPlugin> refunds;
+ try {
+ refunds = pluginApi.searchRefunds(searchKey, offset, limit, buildTenantContext(internalTenantContext));
+ } catch (final PaymentPluginApiException e) {
+ throw new PaymentApiException(e, ErrorCode.PAYMENT_PLUGIN_SEARCH_REFUNDS, pluginName, searchKey);
+ }
+
+ // We need to group the refunds from the plugin by payment id. Since the ordering of the results is unspecified,
+ // we cannot do streaming here unfortunately
+ for (final RefundInfoPlugin refundInfoPlugin : refunds) {
+ if (refundInfoPlugin.getKbPaymentId() == null) {
+ // Garbage from the plugin?
+ log.debug("Plugin {} returned a refund without a kbPaymentId for searchKey {}", pluginName, searchKey);
+ continue;
+ }
+
+ if (refundsByPaymentId.get(refundInfoPlugin.getKbPaymentId()) == null) {
+ refundsByPaymentId.put(refundInfoPlugin.getKbPaymentId(), new LinkedList<RefundInfoPlugin>());
+ }
+ refundsByPaymentId.get(refundInfoPlugin.getKbPaymentId()).add(refundInfoPlugin);
+ }
+
+ return refunds;
+ }
+ },
+ new Function<RefundInfoPlugin, Refund>() {
+ @Override
+ public Refund apply(final RefundInfoPlugin refundInfoPlugin) {
+ if (refundInfoPlugin.getKbPaymentId() == null) {
+ // Garbage from the plugin?
+ log.debug("Plugin {} returned a refund without a kbPaymentId for searchKey {}", pluginName, searchKey);
+ return null;
+ }
+
+ List<RefundModelDao> modelCandidates = refundModelDaosByPaymentId.get(refundInfoPlugin.getKbPaymentId());
+ if (modelCandidates == null) {
+ refundModelDaosByPaymentId.put(refundInfoPlugin.getKbPaymentId(), paymentDao.getRefundsForPayment(refundInfoPlugin.getKbPaymentId(), internalTenantContext));
+ modelCandidates = refundModelDaosByPaymentId.get(refundInfoPlugin.getKbPaymentId());
+ }
+
+ final RefundModelDao model = Iterables.<RefundModelDao>tryFind(modelCandidates,
+ new Predicate<RefundModelDao>() {
+ @Override
+ public boolean apply(final RefundModelDao refundModelDao) {
+ return refundObjectsMatch(refundModelDao, refundInfoPlugin);
+ }
+ }).orNull();
+
+ if (model == null) {
+ log.warn("Unable to find refund for payment id " + refundInfoPlugin.getKbPaymentId() + " present in plugin " + pluginName);
+ return null;
+ }
+
+ return new DefaultRefund(model, refundInfoPlugin);
+ }
+ }
+ );
}
public List<Refund> getAccountRefunds(final Account account, final InternalTenantContext context)
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java b/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java
index 8d3cf23..0aedc0c 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java
@@ -31,12 +31,16 @@ import com.ning.billing.callcontext.InternalTenantContext;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.clock.Clock;
import com.ning.billing.entity.EntityPersistenceException;
+import com.ning.billing.payment.api.Payment;
+import com.ning.billing.payment.api.PaymentMethod;
import com.ning.billing.payment.api.PaymentStatus;
+import com.ning.billing.payment.api.Refund;
import com.ning.billing.payment.api.RefundStatus;
import com.ning.billing.util.cache.CacheControllerDispatcher;
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.DefaultPaginationSqlDaoHelper;
+import com.ning.billing.util.entity.dao.DefaultPaginationSqlDaoHelper.PaginationIteratorBuilder;
import com.ning.billing.util.entity.dao.EntitySqlDao;
import com.ning.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
import com.ning.billing.util.entity.dao.EntitySqlDaoTransactionalJdbiWrapper;
@@ -48,10 +52,12 @@ import com.google.common.collect.Collections2;
public class DefaultPaymentDao implements PaymentDao {
private final EntitySqlDaoTransactionalJdbiWrapper transactionalSqlDao;
+ private final DefaultPaginationSqlDaoHelper paginationHelper;
@Inject
public DefaultPaymentDao(final IDBI dbi, final Clock clock, final CacheControllerDispatcher cacheControllerDispatcher, final NonEntityDao nonEntityDao) {
this.transactionalSqlDao = new EntitySqlDaoTransactionalJdbiWrapper(dbi, clock, cacheControllerDispatcher, nonEntityDao);
+ this.paginationHelper = new DefaultPaginationSqlDaoHelper(transactionalSqlDao);
}
@Override
@@ -160,6 +166,20 @@ public class DefaultPaymentDao implements PaymentDao {
}
@Override
+ public Pagination<RefundModelDao> getRefunds(final String pluginName, final Long offset, final Long limit, final InternalTenantContext context) {
+ return paginationHelper.getPagination(RefundSqlDao.class,
+ new PaginationIteratorBuilder<RefundModelDao, Refund, RefundSqlDao>() {
+ @Override
+ public Iterator<RefundModelDao> build(final RefundSqlDao refundSqlDao, final Long limit) {
+ return refundSqlDao.getByPluginName(pluginName, offset, limit, context);
+ }
+ },
+ offset,
+ limit,
+ context);
+ }
+
+ @Override
public RefundModelDao getRefund(final UUID refundId, final InternalTenantContext context) {
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<RefundModelDao>() {
@Override
@@ -221,30 +241,16 @@ public class DefaultPaymentDao implements PaymentDao {
@Override
public Pagination<PaymentMethodModelDao> getPaymentMethods(final String pluginName, 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 Long inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
- final PaymentMethodSqlDao sqlDao = entitySqlDaoWrapperFactory.become(PaymentMethodSqlDao.class);
- final Iterator<PaymentMethodModelDao> dumbIterator = sqlDao.getByPluginName(pluginName, offset, 1L, 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 PaymentMethodSqlDao paymentMethodSqlDao = transactionalSqlDao.onDemand(PaymentMethodSqlDao.class);
- final Long totalCount = paymentMethodSqlDao.getCount(context);
- final Iterator<PaymentMethodModelDao> results = paymentMethodSqlDao.getByPluginName(pluginName, offset, limit, context);
-
- return new DefaultPagination<PaymentMethodModelDao>(offset, limit, count, totalCount, results);
+ return paginationHelper.getPagination(PaymentMethodSqlDao.class,
+ new PaginationIteratorBuilder<PaymentMethodModelDao, PaymentMethod, PaymentMethodSqlDao>() {
+ @Override
+ public Iterator<PaymentMethodModelDao> build(final PaymentMethodSqlDao paymentMethodSqlDao, final Long limit) {
+ return paymentMethodSqlDao.getByPluginName(pluginName, offset, limit, context);
+ }
+ },
+ offset,
+ limit,
+ context);
}
@Override
@@ -300,30 +306,16 @@ public class DefaultPaymentDao implements PaymentDao {
@Override
public Pagination<PaymentModelDao> getPayments(final String pluginName, 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 Long inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
- final PaymentSqlDao sqlDao = entitySqlDaoWrapperFactory.become(PaymentSqlDao.class);
- final Iterator<PaymentModelDao> dumbIterator = sqlDao.getByPluginName(pluginName, offset, 1L, 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 PaymentSqlDao paymentSqlDao = transactionalSqlDao.onDemand(PaymentSqlDao.class);
- final Long totalCount = paymentSqlDao.getCount(context);
- final Iterator<PaymentModelDao> results = paymentSqlDao.getByPluginName(pluginName, offset, limit, context);
-
- return new DefaultPagination<PaymentModelDao>(offset, limit, count, totalCount, results);
+ return paginationHelper.getPagination(PaymentSqlDao.class,
+ new PaginationIteratorBuilder<PaymentModelDao, Payment, PaymentSqlDao>() {
+ @Override
+ public Iterator<PaymentModelDao> build(final PaymentSqlDao paymentSqlDao, final Long limit) {
+ return paymentSqlDao.getByPluginName(pluginName, offset, limit, context);
+ }
+ },
+ offset,
+ limit,
+ context);
}
@Override
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java
index 613a8d0..17e2503 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java
@@ -55,6 +55,8 @@ public interface PaymentDao {
public void updateRefundStatus(UUID refundId, RefundStatus status, BigDecimal processedAmount, Currency processedCurrency, InternalCallContext context);
+ public Pagination<RefundModelDao> getRefunds(String pluginName, Long offset, Long limit, InternalTenantContext context);
+
public RefundModelDao getRefund(UUID refundId, InternalTenantContext context);
public List<RefundModelDao> getRefundsForPayment(UUID paymentId, InternalTenantContext context);
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/RefundSqlDao.java b/payment/src/main/java/com/ning/billing/payment/dao/RefundSqlDao.java
index 4c3f0cb..4c3d5cb 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/RefundSqlDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/RefundSqlDao.java
@@ -17,18 +17,20 @@
package com.ning.billing.payment.dao;
import java.math.BigDecimal;
+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 com.ning.billing.callcontext.InternalCallContext;
+import com.ning.billing.callcontext.InternalTenantContext;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.payment.api.Refund;
import com.ning.billing.util.audit.ChangeType;
-import com.ning.billing.callcontext.InternalCallContext;
-import com.ning.billing.callcontext.InternalTenantContext;
import com.ning.billing.util.entity.dao.Audited;
import com.ning.billing.util.entity.dao.EntitySqlDao;
import com.ning.billing.util.entity.dao.EntitySqlDaoStringTemplate;
@@ -51,4 +53,13 @@ public interface RefundSqlDao extends EntitySqlDao<RefundModelDao, Refund> {
@SqlQuery
List<RefundModelDao> getRefundsForAccount(@Bind("accountId") final String accountId,
@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<RefundModelDao> getByPluginName(@Bind("pluginName") final String pluginName,
+ @Bind("offset") final Long offset,
+ @Bind("rowCount") final Long rowCount,
+ @BindBean final InternalTenantContext context);
}
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 066000e..a1026cc 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
@@ -228,7 +228,7 @@ public class DefaultNoOpPaymentProviderPlugin implements NoOpPaymentPluginApi {
refundAmount, kbPaymentId.toString(), paymentInfoPlugin.getAmount(), PLUGIN_NAME));
}
- final DefaultNoOpRefundInfoPlugin refundInfoPlugin = new DefaultNoOpRefundInfoPlugin(refundAmount, currency, clock.getUTCNow(), clock.getUTCNow(), RefundPluginStatus.PROCESSED, null);
+ final DefaultNoOpRefundInfoPlugin refundInfoPlugin = new DefaultNoOpRefundInfoPlugin(kbPaymentId, refundAmount, currency, clock.getUTCNow(), clock.getUTCNow(), RefundPluginStatus.PROCESSED, null);
refunds.put(kbPaymentId.toString(), refundInfoPlugin);
return refundInfoPlugin;
@@ -238,4 +238,26 @@ public class DefaultNoOpPaymentProviderPlugin implements NoOpPaymentPluginApi {
public List<RefundInfoPlugin> getRefundInfo(final UUID kbAccountId, final UUID kbPaymentId, final TenantContext context) {
return ImmutableList.<RefundInfoPlugin>copyOf(refunds.get(kbPaymentId.toString()));
}
+
+ @Override
+ public Pagination<RefundInfoPlugin> searchRefunds(final String searchKey, final Long offset, final Long limit, final TenantContext tenantContext) throws PaymentPluginApiException {
+ final ImmutableList<RefundInfoPlugin> allResults = ImmutableList.<RefundInfoPlugin>copyOf(Iterables.<RefundInfoPlugin>filter(Iterables.<RefundInfoPlugin>concat(refunds.values()), new Predicate<RefundInfoPlugin>() {
+ @Override
+ public boolean apply(final RefundInfoPlugin input) {
+ return (input.getFirstRefundReferenceId() != null && input.getFirstRefundReferenceId().contains(searchKey)) ||
+ (input.getSecondRefundReferenceId() != null && input.getSecondRefundReferenceId().contains(searchKey));
+ }
+ }));
+
+ final List<RefundInfoPlugin> results;
+ if (offset >= allResults.size()) {
+ results = ImmutableList.<RefundInfoPlugin>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<RefundInfoPlugin>(offset, limit, (long) results.size(), (long) refunds.values().size(), results.iterator());
+ }
}
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/DefaultNoOpRefundInfoPlugin.java b/payment/src/main/java/com/ning/billing/payment/provider/DefaultNoOpRefundInfoPlugin.java
index c3294d4..698804d 100644
--- a/payment/src/main/java/com/ning/billing/payment/provider/DefaultNoOpRefundInfoPlugin.java
+++ b/payment/src/main/java/com/ning/billing/payment/provider/DefaultNoOpRefundInfoPlugin.java
@@ -17,6 +17,7 @@
package com.ning.billing.payment.provider;
import java.math.BigDecimal;
+import java.util.UUID;
import org.joda.time.DateTime;
@@ -26,6 +27,7 @@ import com.ning.billing.payment.plugin.api.RefundPluginStatus;
public class DefaultNoOpRefundInfoPlugin implements RefundInfoPlugin {
+ private final UUID kbPaymentId;
private final BigDecimal amount;
private final Currency currency;
private final DateTime effectiveDate;
@@ -33,8 +35,9 @@ public class DefaultNoOpRefundInfoPlugin implements RefundInfoPlugin {
private final RefundPluginStatus status;
private final String error;
- public DefaultNoOpRefundInfoPlugin(final BigDecimal amount, final Currency currency, final DateTime effectiveDate,
+ public DefaultNoOpRefundInfoPlugin(final UUID kbPaymentId, final BigDecimal amount, final Currency currency, final DateTime effectiveDate,
final DateTime createdDate, final RefundPluginStatus status, final String error) {
+ this.kbPaymentId = kbPaymentId;
this.amount = amount;
this.currency = currency;
this.effectiveDate = effectiveDate;
@@ -44,6 +47,11 @@ public class DefaultNoOpRefundInfoPlugin implements RefundInfoPlugin {
}
@Override
+ public UUID getKbPaymentId() {
+ return kbPaymentId;
+ }
+
+ @Override
public BigDecimal getAmount() {
return amount;
}
@@ -79,15 +87,21 @@ public class DefaultNoOpRefundInfoPlugin implements RefundInfoPlugin {
}
@Override
- public String getReferenceId() {
+ public String getFirstRefundReferenceId() {
+ return null;
+ }
+
+ @Override
+ public String getSecondRefundReferenceId() {
return null;
}
@Override
public String toString() {
- final StringBuilder sb = new StringBuilder();
- sb.append("DefaultNoOpRefundInfoPlugin");
- sb.append("{amount=").append(amount);
+ final StringBuilder sb = new StringBuilder("DefaultNoOpRefundInfoPlugin{");
+ sb.append("kbPaymentId=").append(kbPaymentId);
+ sb.append(", amount=").append(amount);
+ sb.append(", currency=").append(currency);
sb.append(", effectiveDate=").append(effectiveDate);
sb.append(", createdDate=").append(createdDate);
sb.append(", status=").append(status);
@@ -107,18 +121,24 @@ public class DefaultNoOpRefundInfoPlugin implements RefundInfoPlugin {
final DefaultNoOpRefundInfoPlugin that = (DefaultNoOpRefundInfoPlugin) o;
- if (amount != null ? !amount.equals(that.amount) : that.amount != null) {
+ if (amount != null ? amount.compareTo(that.amount) != 0 : that.amount != null) {
+ return false;
+ }
+ if (createdDate != null ? createdDate.compareTo(that.createdDate) != 0 : that.createdDate != null) {
return false;
}
- if (createdDate != null ? !createdDate.equals(that.createdDate) : that.createdDate != null) {
+ if (currency != that.currency) {
return false;
}
- if (effectiveDate != null ? !effectiveDate.equals(that.effectiveDate) : that.effectiveDate != null) {
+ if (effectiveDate != null ? effectiveDate.compareTo(that.effectiveDate) != 0 : that.effectiveDate != null) {
return false;
}
if (error != null ? !error.equals(that.error) : that.error != null) {
return false;
}
+ if (kbPaymentId != null ? !kbPaymentId.equals(that.kbPaymentId) : that.kbPaymentId != null) {
+ return false;
+ }
if (status != that.status) {
return false;
}
@@ -128,7 +148,9 @@ public class DefaultNoOpRefundInfoPlugin implements RefundInfoPlugin {
@Override
public int hashCode() {
- int result = amount != null ? amount.hashCode() : 0;
+ int result = kbPaymentId != null ? kbPaymentId.hashCode() : 0;
+ result = 31 * result + (amount != null ? amount.hashCode() : 0);
+ result = 31 * result + (currency != null ? currency.hashCode() : 0);
result = 31 * result + (effectiveDate != null ? effectiveDate.hashCode() : 0);
result = 31 * result + (createdDate != null ? createdDate.hashCode() : 0);
result = 31 * result + (status != null ? status.hashCode() : 0);
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 ba12401..1d4572e 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
@@ -75,7 +75,7 @@ public class ExternalPaymentProviderPlugin implements PaymentPluginApi {
@Override
public RefundInfoPlugin processRefund(final UUID kbAccountId, final UUID kbPaymentId, final BigDecimal refundAmount, final Currency currency, final CallContext context) throws PaymentPluginApiException {
- return new DefaultNoOpRefundInfoPlugin(BigDecimal.ZERO, currency, clock.getUTCNow(), clock.getUTCNow(), RefundPluginStatus.PROCESSED, null);
+ return new DefaultNoOpRefundInfoPlugin(kbPaymentId, BigDecimal.ZERO, currency, clock.getUTCNow(), clock.getUTCNow(), RefundPluginStatus.PROCESSED, null);
}
@Override
@@ -84,6 +84,11 @@ public class ExternalPaymentProviderPlugin implements PaymentPluginApi {
}
@Override
+ public Pagination<RefundInfoPlugin> searchRefunds(final String searchKey, final Long offset, final Long limit, final TenantContext tenantContext) throws PaymentPluginApiException {
+ return new DefaultPagination<RefundInfoPlugin>(offset, limit, 0L, 0L, Iterators.<RefundInfoPlugin>emptyIterator());
+ }
+
+ @Override
public void addPaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final PaymentMethodPlugin paymentMethodProps, final boolean setDefault, final CallContext context) throws PaymentPluginApiException {
}
diff --git a/payment/src/main/resources/com/ning/billing/payment/dao/RefundSqlDao.sql.stg b/payment/src/main/resources/com/ning/billing/payment/dao/RefundSqlDao.sql.stg
index a288ea5..dc9025b 100644
--- a/payment/src/main/resources/com/ning/billing/payment/dao/RefundSqlDao.sql.stg
+++ b/payment/src/main/resources/com/ning/billing/payment/dao/RefundSqlDao.sql.stg
@@ -63,3 +63,14 @@ where account_id = :accountId
<defaultOrderBy()>
;
>>
+
+getByPluginName(pluginName, offset, rowCount) ::= <<
+select SQL_CALC_FOUND_ROWS
+<allTableFields("t.")>
+from <tableName()> t
+join payment_methods pm on pm.id = t.payment_method_id
+where pm.plugin_name = :pluginName
+order by record_id
+limit :offset, :rowCount
+;
+>>
\ No newline at end of file
diff --git a/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java b/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java
index 3e14944..431860e 100644
--- a/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java
+++ b/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java
@@ -197,6 +197,11 @@ public class MockPaymentDao implements PaymentDao {
}
@Override
+ public Pagination<RefundModelDao> getRefunds(final String pluginName, final Long offset, final Long limit, final InternalTenantContext context) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public RefundModelDao getRefund(final UUID refundId, final InternalTenantContext context) {
return null;
}
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 39d330b..246cfd3 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
@@ -121,7 +121,7 @@ public class MockPaymentProviderPlugin implements NoOpPaymentPluginApi {
final ImmutableList<PaymentInfoPlugin> results = ImmutableList.<PaymentInfoPlugin>copyOf(Iterables.<PaymentInfoPlugin>filter(payments.values(), new Predicate<PaymentInfoPlugin>() {
@Override
public boolean apply(final PaymentInfoPlugin input) {
- return (input.getKbPaymentId() != null && input.getKbPaymentId().equals(searchKey)) ||
+ return (input.getKbPaymentId() != null && input.getKbPaymentId().toString().equals(searchKey)) ||
(input.getFirstPaymentReferenceId() != null && input.getFirstPaymentReferenceId().contains(searchKey)) ||
(input.getSecondPaymentReferenceId() != null && input.getSecondPaymentReferenceId().contains(searchKey));
}
@@ -202,7 +202,7 @@ public class MockPaymentProviderPlugin implements NoOpPaymentPluginApi {
refundAmount, kbPaymentId.toString(), paymentInfoPlugin.getAmount(), PLUGIN_NAME));
}
- final DefaultNoOpRefundInfoPlugin refundInfoPlugin = new DefaultNoOpRefundInfoPlugin(refundAmount, currency, clock.getUTCNow(), clock.getUTCNow(), RefundPluginStatus.PROCESSED, null);
+ final DefaultNoOpRefundInfoPlugin refundInfoPlugin = new DefaultNoOpRefundInfoPlugin(kbPaymentId, refundAmount, currency, clock.getUTCNow(), clock.getUTCNow(), RefundPluginStatus.PROCESSED, null);
refunds.put(kbPaymentId.toString(), refundInfoPlugin);
return refundInfoPlugin;
@@ -212,4 +212,17 @@ public class MockPaymentProviderPlugin implements NoOpPaymentPluginApi {
public List<RefundInfoPlugin> getRefundInfo(final UUID kbAccountId, final UUID kbPaymentId, final TenantContext context) throws PaymentPluginApiException {
return Collections.<RefundInfoPlugin>emptyList();
}
+
+ @Override
+ public Pagination<RefundInfoPlugin> searchRefunds(final String searchKey, final Long offset, final Long limit, final TenantContext tenantContext) throws PaymentPluginApiException {
+ final ImmutableList<RefundInfoPlugin> results = ImmutableList.<RefundInfoPlugin>copyOf(Iterables.<RefundInfoPlugin>filter(refunds.values(), new Predicate<RefundInfoPlugin>() {
+ @Override
+ public boolean apply(final RefundInfoPlugin input) {
+ return (input.getKbPaymentId() != null && input.getKbPaymentId().toString().equals(searchKey)) ||
+ (input.getFirstRefundReferenceId() != null && input.getFirstRefundReferenceId().contains(searchKey)) ||
+ (input.getSecondRefundReferenceId() != null && input.getSecondRefundReferenceId().contains(searchKey));
+ }
+ }));
+ return DefaultPagination.<RefundInfoPlugin>build(offset, limit, results);
+ }
}
pom.xml 2(+1 -1)
diff --git a/pom.xml b/pom.xml
index 78df4c9..a91fd65 100644
--- a/pom.xml
+++ b/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill-oss-parent</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.5.15</version>
+ <version>0.8.0-SNAPSHOT</version>
</parent>
<artifactId>killbill</artifactId>
<version>0.8.8-SNAPSHOT</version>
diff --git a/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java b/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java
index 8c30cba..667c77d 100644
--- a/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java
+++ b/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java
@@ -19,10 +19,6 @@ package com.ning.billing.server.modules;
import javax.servlet.ServletContext;
import javax.sql.DataSource;
-import com.ning.billing.clock.Clock;
-import com.ning.billing.clock.ClockMock;
-import com.ning.billing.currency.glue.CurrencyModule;
-import com.ning.billing.entitlement.glue.DefaultEntitlementModule;
import org.skife.config.ConfigSource;
import org.skife.config.SimplePropertyConfigSource;
import org.skife.jdbi.v2.DBI;
@@ -31,21 +27,26 @@ import org.skife.jdbi.v2.IDBI;
import com.ning.billing.account.glue.DefaultAccountModule;
import com.ning.billing.beatrix.glue.BeatrixModule;
import com.ning.billing.catalog.glue.CatalogModule;
-import com.ning.billing.jaxrs.resources.SubscriptionResource;
-import com.ning.billing.jaxrs.resources.TestResource;
-import com.ning.billing.subscription.glue.DefaultSubscriptionModule;
+import com.ning.billing.clock.Clock;
+import com.ning.billing.clock.ClockMock;
+import com.ning.billing.currency.glue.CurrencyModule;
+import com.ning.billing.entitlement.glue.DefaultEntitlementModule;
import com.ning.billing.invoice.glue.DefaultInvoiceModule;
import com.ning.billing.jaxrs.resources.AccountResource;
import com.ning.billing.jaxrs.resources.BundleResource;
import com.ning.billing.jaxrs.resources.CatalogResource;
+import com.ning.billing.jaxrs.resources.CustomFieldResource;
import com.ning.billing.jaxrs.resources.ExportResource;
import com.ning.billing.jaxrs.resources.InvoiceResource;
import com.ning.billing.jaxrs.resources.PaymentMethodResource;
import com.ning.billing.jaxrs.resources.PaymentResource;
import com.ning.billing.jaxrs.resources.PluginResource;
import com.ning.billing.jaxrs.resources.RefundResource;
+import com.ning.billing.jaxrs.resources.SubscriptionResource;
+import com.ning.billing.jaxrs.resources.TagDefinitionResource;
import com.ning.billing.jaxrs.resources.TagResource;
import com.ning.billing.jaxrs.resources.TenantResource;
+import com.ning.billing.jaxrs.resources.TestResource;
import com.ning.billing.jaxrs.util.KillbillEventHandler;
import com.ning.billing.junction.glue.DefaultJunctionModule;
import com.ning.billing.osgi.glue.DefaultOSGIModule;
@@ -54,6 +55,7 @@ import com.ning.billing.payment.glue.PaymentModule;
import com.ning.billing.server.DefaultServerService;
import com.ning.billing.server.ServerService;
import com.ning.billing.server.notifications.PushNotificationListener;
+import com.ning.billing.subscription.glue.DefaultSubscriptionModule;
import com.ning.billing.tenant.glue.TenantModule;
import com.ning.billing.usage.glue.UsageModule;
import com.ning.billing.util.email.EmailModule;
@@ -115,7 +117,9 @@ public class KillbillServerModule extends AbstractModule {
bind(BundleResource.class).asEagerSingleton();
bind(SubscriptionResource.class).asEagerSingleton();
bind(InvoiceResource.class).asEagerSingleton();
+ bind(CustomFieldResource.class).asEagerSingleton();
bind(TagResource.class).asEagerSingleton();
+ bind(TagDefinitionResource.class).asEagerSingleton();
bind(CatalogResource.class).asEagerSingleton();
bind(PaymentMethodResource.class).asEagerSingleton();
bind(PaymentResource.class).asEagerSingleton();
diff --git a/util/src/main/java/com/ning/billing/util/customfield/api/DefaultCustomFieldUserApi.java b/util/src/main/java/com/ning/billing/util/customfield/api/DefaultCustomFieldUserApi.java
index e0e543d..903659d 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/api/DefaultCustomFieldUserApi.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/api/DefaultCustomFieldUserApi.java
@@ -16,6 +16,7 @@
package com.ning.billing.util.customfield.api;
+import java.util.Collection;
import java.util.List;
import java.util.UUID;
@@ -29,14 +30,25 @@ import com.ning.billing.util.customfield.CustomField;
import com.ning.billing.util.customfield.StringCustomField;
import com.ning.billing.util.customfield.dao.CustomFieldDao;
import com.ning.billing.util.customfield.dao.CustomFieldModelDao;
+import com.ning.billing.util.entity.Pagination;
+import com.ning.billing.util.entity.dao.DefaultPaginationHelper.SourcePaginationBuilder;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.inject.Inject;
+import static com.ning.billing.util.entity.dao.DefaultPaginationHelper.getEntityPaginationNoException;
+
public class DefaultCustomFieldUserApi implements CustomFieldUserApi {
+ private static final Function<CustomFieldModelDao, CustomField> CUSTOM_FIELD_MODEL_DAO_CUSTOM_FIELD_FUNCTION = new Function<CustomFieldModelDao, CustomField>() {
+ @Override
+ public CustomField apply(final CustomFieldModelDao input) {
+ return new StringCustomField(input);
+ }
+ };
+
private final InternalCallContextFactory internalCallContextFactory;
private final CustomFieldDao customFieldDao;
@@ -47,6 +59,30 @@ public class DefaultCustomFieldUserApi implements CustomFieldUserApi {
}
@Override
+ public Pagination<CustomField> searchCustomFields(final String searchKey, final Long offset, final Long limit, final TenantContext context) {
+ return getEntityPaginationNoException(limit,
+ new SourcePaginationBuilder<CustomFieldModelDao, CustomFieldApiException>() {
+ @Override
+ public Pagination<CustomFieldModelDao> build() {
+ return customFieldDao.searchCustomFields(searchKey, offset, limit, internalCallContextFactory.createInternalTenantContext(context));
+ }
+ },
+ CUSTOM_FIELD_MODEL_DAO_CUSTOM_FIELD_FUNCTION);
+ }
+
+ @Override
+ public Pagination<CustomField> getCustomFields(final Long offset, final Long limit, final TenantContext context) {
+ return getEntityPaginationNoException(limit,
+ new SourcePaginationBuilder<CustomFieldModelDao, CustomFieldApiException>() {
+ @Override
+ public Pagination<CustomFieldModelDao> build() {
+ return customFieldDao.get(offset, limit, internalCallContextFactory.createInternalTenantContext(context));
+ }
+ },
+ CUSTOM_FIELD_MODEL_DAO_CUSTOM_FIELD_FUNCTION);
+ }
+
+ @Override
public void addCustomFields(final List<CustomField> fields, final CallContext context) throws CustomFieldApiException {
// TODO make it transactional
for (final CustomField cur : fields) {
@@ -69,13 +105,7 @@ public class DefaultCustomFieldUserApi implements CustomFieldUserApi {
return withCustomFieldsTransform(customFieldDao.getCustomFieldsForAccount(internalCallContextFactory.createInternalTenantContext(accountId, context)));
}
- private List<CustomField> withCustomFieldsTransform(List<CustomFieldModelDao> input) {
- return ImmutableList.<CustomField>copyOf(Collections2.transform(input, new Function<CustomFieldModelDao, CustomField>() {
- @Override
- public CustomField apply(final CustomFieldModelDao input) {
- return new StringCustomField(input);
- }
- }));
+ private List<CustomField> withCustomFieldsTransform(final Collection<CustomFieldModelDao> input) {
+ return ImmutableList.<CustomField>copyOf(Collections2.transform(input, CUSTOM_FIELD_MODEL_DAO_CUSTOM_FIELD_FUNCTION));
}
-
}
diff --git a/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldDao.java b/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldDao.java
index b64b5b6..20e962f 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldDao.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldDao.java
@@ -20,13 +20,16 @@ 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.Pagination;
import com.ning.billing.util.entity.dao.EntityDao;
public interface CustomFieldDao extends EntityDao<CustomFieldModelDao, CustomField, CustomFieldApiException> {
+ public Pagination<CustomFieldModelDao> searchCustomFields(String searchKey, Long offset, Long limit, InternalTenantContext context);
+
public List<CustomFieldModelDao> getCustomFieldsForObject(final UUID objectId, final ObjectType objectType, final InternalTenantContext context);
public List<CustomFieldModelDao> getCustomFieldsForAccountType(final ObjectType objectType, final InternalTenantContext context);
diff --git a/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.java b/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.java
index 6f11bcf..b54503c 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.java
@@ -16,12 +16,15 @@
package com.ning.billing.util.customfield.dao;
+import java.util.Iterator;
import java.util.List;
import java.util.UUID;
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.customizers.Define;
+import org.skife.jdbi.v2.sqlobject.customizers.FetchSize;
import com.ning.billing.ObjectType;
import com.ning.billing.callcontext.InternalTenantContext;
@@ -36,4 +39,13 @@ public interface CustomFieldSqlDao extends EntitySqlDao<CustomFieldModelDao, Cus
List<CustomFieldModelDao> getCustomFieldsForObject(@Bind("objectId") UUID objectId,
@Bind("objectType") ObjectType objectType,
@BindBean InternalTenantContext internalTenantContext);
+
+ @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<CustomFieldModelDao> searchCustomFields(@Define("searchKey") final String searchKey,
+ @Bind("offset") final Long offset,
+ @Bind("rowCount") final Long rowCount,
+ @BindBean final InternalTenantContext context);
}
diff --git a/util/src/main/java/com/ning/billing/util/customfield/dao/DefaultCustomFieldDao.java b/util/src/main/java/com/ning/billing/util/customfield/dao/DefaultCustomFieldDao.java
index 0aa1076..d3642a4 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/dao/DefaultCustomFieldDao.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/dao/DefaultCustomFieldDao.java
@@ -16,6 +16,7 @@
package com.ning.billing.util.customfield.dao;
+import java.util.Iterator;
import java.util.List;
import java.util.UUID;
@@ -29,22 +30,24 @@ import com.ning.billing.BillingExceptionBase;
import com.ning.billing.ErrorCode;
import com.ning.billing.ObjectType;
import com.ning.billing.bus.api.PersistentBus;
-import com.ning.billing.util.api.CustomFieldApiException;
-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.BusInternalEvent;
+import com.ning.billing.util.api.CustomFieldApiException;
+import com.ning.billing.util.audit.ChangeType;
+import com.ning.billing.util.cache.CacheControllerDispatcher;
import com.ning.billing.util.customfield.CustomField;
import com.ning.billing.util.customfield.api.DefaultCustomFieldCreationEvent;
import com.ning.billing.util.customfield.api.DefaultCustomFieldDeletionEvent;
import com.ning.billing.util.dao.NonEntityDao;
+import com.ning.billing.util.entity.Pagination;
+import com.ning.billing.util.entity.dao.DefaultPaginationSqlDaoHelper.PaginationIteratorBuilder;
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.BusInternalEvent;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
@@ -53,7 +56,7 @@ import com.google.inject.Inject;
public class DefaultCustomFieldDao extends EntityDaoBase<CustomFieldModelDao, CustomField, CustomFieldApiException> implements CustomFieldDao {
- private final static Logger log = LoggerFactory.getLogger(DefaultCustomFieldDao.class);
+ private static final Logger log = LoggerFactory.getLogger(DefaultCustomFieldDao.class);
private final PersistentBus bus;
@@ -127,4 +130,17 @@ public class DefaultCustomFieldDao extends EntityDaoBase<CustomFieldModelDao, Cu
}
+ @Override
+ public Pagination<CustomFieldModelDao> searchCustomFields(final String searchKey, final Long offset, final Long limit, final InternalTenantContext context) {
+ return paginationHelper.getPagination(CustomFieldSqlDao.class,
+ new PaginationIteratorBuilder<CustomFieldModelDao, CustomField, CustomFieldSqlDao>() {
+ @Override
+ public Iterator<CustomFieldModelDao> build(final CustomFieldSqlDao customFieldSqlDao, final Long limit) {
+ return customFieldSqlDao.searchCustomFields(searchKey, offset, limit, context);
+ }
+ },
+ offset,
+ limit,
+ context);
+ }
}
diff --git a/util/src/main/java/com/ning/billing/util/entity/dao/DefaultPaginationHelper.java b/util/src/main/java/com/ning/billing/util/entity/dao/DefaultPaginationHelper.java
new file mode 100644
index 0000000..1383955
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/entity/dao/DefaultPaginationHelper.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2010-2014 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.dao;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ning.billing.BillingExceptionBase;
+import com.ning.billing.util.customfield.ShouldntHappenException;
+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.base.Function;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterators;
+
+public class DefaultPaginationHelper {
+
+ private static final Logger log = LoggerFactory.getLogger(DefaultPaginationHelper.class);
+
+ public abstract static class EntityPaginationBuilder<E extends Entity, T extends BillingExceptionBase> {
+
+ public abstract Pagination<E> build(final Long offset, final Long limit, final String pluginName) throws T;
+ }
+
+ public static <E extends Entity, T extends BillingExceptionBase> Pagination<E> getEntityPaginationFromPlugins(final Iterable<String> plugins, final Long offset, final Long limit, final EntityPaginationBuilder<E, T> entityPaginationBuilder) {
+ // 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<E> allResults = new LinkedList<E>();
+ Long totalNbRecords = 0L;
+ Long maxNbRecords = 0L;
+
+ // Search in all plugins (we treat the full set of results as a union with respect to offset/limit)
+ boolean firstSearch = true;
+ for (final String pluginName : plugins) {
+ try {
+ final Pagination<E> pages;
+ if (allResults.size() >= limit) {
+ // We have enough results, we just keep going (limit 1) to get the stats
+ pages = entityPaginationBuilder.build(firstSearch ? offset : 0L, 1L, pluginName);
+ // Required to close database connections
+ ImmutableList.<E>copyOf(pages);
+ } else {
+ pages = entityPaginationBuilder.build(firstSearch ? offset : 0L, limit - allResults.size(), pluginName);
+ allResults.addAll(ImmutableList.<E>copyOf(pages));
+ }
+ firstSearch = false;
+ totalNbRecords += pages.getTotalNbRecords();
+ maxNbRecords += pages.getMaxNbRecords();
+ } catch (final BillingExceptionBase e) {
+ log.warn("Error while searching plugin " + pluginName, e);
+ // Non-fatal, continue to search other plugins
+ }
+ }
+
+ return new DefaultPagination<E>(offset, limit, totalNbRecords, maxNbRecords, allResults.iterator());
+ }
+
+ public abstract static class SourcePaginationBuilder<O, T extends BillingExceptionBase> {
+
+ public abstract Pagination<O> build() throws T;
+ }
+
+ public static <E extends Entity, O, T extends BillingExceptionBase> Pagination<E> getEntityPagination(final Long limit,
+ final SourcePaginationBuilder<O, T> sourcePaginationBuilder,
+ final Function<O, E> function) throws T {
+ final Pagination<O> modelsDao = sourcePaginationBuilder.build();
+
+ return new DefaultPagination<E>(modelsDao,
+ limit,
+ Iterators.<E>filter(Iterators.<O, E>transform(modelsDao.iterator(), function),
+ Predicates.<E>notNull()));
+ }
+
+ public static <E extends Entity, O, T extends BillingExceptionBase> Pagination<E> getEntityPaginationNoException(final Long limit,
+ final SourcePaginationBuilder<O, T> sourcePaginationBuilder,
+ final Function<O, E> function) {
+ try {
+ return getEntityPagination(limit, sourcePaginationBuilder, function);
+ } catch (final BillingExceptionBase e) {
+ throw new ShouldntHappenException("No exception expected" + e);
+ }
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/entity/dao/DefaultPaginationSqlDaoHelper.java b/util/src/main/java/com/ning/billing/util/entity/dao/DefaultPaginationSqlDaoHelper.java
new file mode 100644
index 0000000..e245ffd
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/entity/dao/DefaultPaginationSqlDaoHelper.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2010-2014 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.dao;
+
+import java.util.Iterator;
+
+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;
+
+public class DefaultPaginationSqlDaoHelper {
+
+ private final EntitySqlDaoTransactionalJdbiWrapper transactionalSqlDao;
+
+ public DefaultPaginationSqlDaoHelper(final EntitySqlDaoTransactionalJdbiWrapper transactionalSqlDao) {
+ this.transactionalSqlDao = transactionalSqlDao;
+ }
+
+ public <E extends Entity, M extends EntityModelDao<E>, S extends EntitySqlDao<M, E>> Pagination<M> getPagination(final Class<? extends EntitySqlDao<M, E>> sqlDaoClazz,
+ final PaginationIteratorBuilder<M, E, S> paginationIteratorBuilder,
+ 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 Long inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+ final EntitySqlDao<M, E> sqlDao = entitySqlDaoWrapperFactory.become(sqlDaoClazz);
+ // TODO lame cast, but couldn't make sqlDaoClazz a Class<? extends S>
+ final Iterator<M> dumbIterator = paginationIteratorBuilder.build((S) sqlDao, 1L);
+ // 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(sqlDaoClazz);
+ final Long totalCount = sqlDao.getCount(context);
+ final Iterator<M> results = paginationIteratorBuilder.build((S) sqlDao, limit);
+
+ return new DefaultPagination<M>(offset, limit, count, totalCount, results);
+ }
+
+ public abstract static class PaginationIteratorBuilder<M extends EntityModelDao<E>, E extends Entity, S extends EntitySqlDao<M, E>> {
+
+ public abstract Iterator<M> build(final S sqlDao,
+ final Long limit);
+ }
+}
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 d494c4e..a0721bc 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
@@ -26,16 +26,19 @@ 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;
+import com.ning.billing.util.entity.dao.DefaultPaginationSqlDaoHelper.PaginationIteratorBuilder;
public abstract class EntityDaoBase<M extends EntityModelDao<E>, E extends Entity, U extends BillingExceptionBase> implements EntityDao<M, E, U> {
protected final EntitySqlDaoTransactionalJdbiWrapper transactionalSqlDao;
+ protected final DefaultPaginationSqlDaoHelper paginationHelper;
private final Class<? extends EntitySqlDao<M, E>> realSqlDao;
public EntityDaoBase(final EntitySqlDaoTransactionalJdbiWrapper transactionalSqlDao, final Class<? extends EntitySqlDao<M, E>> realSqlDao) {
this.transactionalSqlDao = transactionalSqlDao;
this.realSqlDao = realSqlDao;
+ this.paginationHelper = new DefaultPaginationSqlDaoHelper(transactionalSqlDao);
}
@Override
@@ -130,30 +133,16 @@ public abstract class EntityDaoBase<M extends EntityModelDao<E>, E extends Entit
@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 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);
+ return paginationHelper.getPagination(realSqlDao,
+ new PaginationIteratorBuilder<M, E, EntitySqlDao<M, E>>() {
+ @Override
+ public Iterator<M> build(final EntitySqlDao<M, E> sqlDao, final Long limit) {
+ return sqlDao.get(offset, limit, getNaturalOrderingColumns(), context);
+ }
+ },
+ offset,
+ limit,
+ context);
}
@Override
diff --git a/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagUserApi.java b/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagUserApi.java
index daf484e..50df856 100644
--- a/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagUserApi.java
+++ b/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagUserApi.java
@@ -29,6 +29,8 @@ import com.ning.billing.util.api.TagUserApi;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.InternalCallContextFactory;
import com.ning.billing.util.callcontext.TenantContext;
+import com.ning.billing.util.entity.Pagination;
+import com.ning.billing.util.entity.dao.DefaultPaginationHelper.SourcePaginationBuilder;
import com.ning.billing.util.tag.ControlTagType;
import com.ning.billing.util.tag.DefaultControlTag;
import com.ning.billing.util.tag.DefaultTagDefinition;
@@ -46,8 +48,19 @@ import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.inject.Inject;
+import static com.ning.billing.util.entity.dao.DefaultPaginationHelper.getEntityPaginationNoException;
+
public class DefaultTagUserApi implements TagUserApi {
+ private static final Function<TagModelDao, Tag> TAG_MODEL_DAO_TAG_FUNCTION = new Function<TagModelDao, Tag>() {
+ @Override
+ public Tag apply(final TagModelDao input) {
+ return TagModelDaoHelper.isControlTag(input.getTagDefinitionId()) ?
+ new DefaultControlTag(input.getId(), ControlTagType.getTypeFromId(input.getTagDefinitionId()), input.getObjectType(), input.getObjectId(), input.getCreatedDate()) :
+ new DescriptiveTag(input.getId(), input.getTagDefinitionId(), input.getObjectType(), input.getObjectId(), input.getCreatedDate());
+ }
+ };
+
private final InternalCallContextFactory internalCallContextFactory;
private final TagDefinitionDao tagDefinitionDao;
private final TagDao tagDao;
@@ -127,6 +140,30 @@ public class DefaultTagUserApi implements TagUserApi {
}
@Override
+ public Pagination<Tag> searchTags(final String searchKey, final Long offset, final Long limit, final TenantContext context) {
+ return getEntityPaginationNoException(limit,
+ new SourcePaginationBuilder<TagModelDao, TagApiException>() {
+ @Override
+ public Pagination<TagModelDao> build() {
+ return tagDao.searchTags(searchKey, offset, limit, internalCallContextFactory.createInternalTenantContext(context));
+ }
+ },
+ TAG_MODEL_DAO_TAG_FUNCTION);
+ }
+
+ @Override
+ public Pagination<Tag> getTags(final Long offset, final Long limit, final TenantContext context) {
+ return getEntityPaginationNoException(limit,
+ new SourcePaginationBuilder<TagModelDao, TagApiException>() {
+ @Override
+ public Pagination<TagModelDao> build() {
+ return tagDao.get(offset, limit, internalCallContextFactory.createInternalTenantContext(context));
+ }
+ },
+ TAG_MODEL_DAO_TAG_FUNCTION);
+ }
+
+ @Override
public void removeTags(final UUID objectId, final ObjectType objectType, final Collection<UUID> tagDefinitionIds, final CallContext context) throws TagApiException {
// TODO: consider making this batch
for (final UUID tagDefinitionId : tagDefinitionIds) {
@@ -156,14 +193,7 @@ public class DefaultTagUserApi implements TagUserApi {
return withModelTransform(tagDao.getTagsForAccount(includedDeleted, internalCallContextFactory.createInternalTenantContext(accountId, context)));
}
- private List<Tag> withModelTransform(final List<TagModelDao> input) {
- return ImmutableList.<Tag>copyOf(Collections2.transform(input, new Function<TagModelDao, Tag>() {
- @Override
- public Tag apply(final TagModelDao input) {
- return TagModelDaoHelper.isControlTag(input.getTagDefinitionId()) ?
- new DefaultControlTag(input.getId(), ControlTagType.getTypeFromId(input.getTagDefinitionId()), input.getObjectType(), input.getObjectId(), input.getCreatedDate()) :
- new DescriptiveTag(input.getId(), input.getTagDefinitionId(), input.getObjectType(), input.getObjectId(), input.getCreatedDate());
- }
- }));
+ private List<Tag> withModelTransform(final Collection<TagModelDao> input) {
+ return ImmutableList.<Tag>copyOf(Collections2.transform(input, TAG_MODEL_DAO_TAG_FUNCTION));
}
}
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/DefaultTagDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/DefaultTagDao.java
index d1307ec..e559df7 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/DefaultTagDao.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/DefaultTagDao.java
@@ -16,6 +16,7 @@
package com.ning.billing.util.tag.dao;
+import java.util.Iterator;
import java.util.List;
import java.util.UUID;
@@ -35,6 +36,8 @@ import com.ning.billing.util.api.TagApiException;
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.Pagination;
+import com.ning.billing.util.entity.dao.DefaultPaginationSqlDaoHelper.PaginationIteratorBuilder;
import com.ning.billing.util.entity.dao.EntityDaoBase;
import com.ning.billing.util.entity.dao.EntitySqlDao;
import com.ning.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
@@ -213,4 +216,18 @@ public class DefaultTagDao extends EntityDaoBase<TagModelDao, Tag, TagApiExcepti
});
}
+
+ @Override
+ public Pagination<TagModelDao> searchTags(final String searchKey, final Long offset, final Long limit, final InternalTenantContext context) {
+ return paginationHelper.getPagination(TagSqlDao.class,
+ new PaginationIteratorBuilder<TagModelDao, Tag, TagSqlDao>() {
+ @Override
+ public Iterator<TagModelDao> build(final TagSqlDao tagSqlDao, final Long limit) {
+ return tagSqlDao.searchTags(searchKey, offset, limit, context);
+ }
+ },
+ offset,
+ limit,
+ context);
+ }
}
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/TagDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/TagDao.java
index 0077072..b1b0fba 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/TagDao.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagDao.java
@@ -23,14 +23,15 @@ import com.ning.billing.ObjectType;
import com.ning.billing.callcontext.InternalCallContext;
import com.ning.billing.callcontext.InternalTenantContext;
import com.ning.billing.util.api.TagApiException;
+import com.ning.billing.util.entity.Pagination;
+import com.ning.billing.util.entity.dao.EntityDao;
+import com.ning.billing.util.tag.Tag;
-public interface TagDao {
-
- void create(TagModelDao tag, InternalCallContext context) throws TagApiException;
+public interface TagDao extends EntityDao<TagModelDao, Tag, TagApiException> {
void deleteTag(UUID objectId, ObjectType objectType, UUID tagDefinition, InternalCallContext context) throws TagApiException;
- TagModelDao getById(UUID tagId, InternalTenantContext context);
+ Pagination<TagModelDao> searchTags(String searchKey, Long offset, Long limit, InternalTenantContext context);
List<TagModelDao> getTagsForObject(UUID objectId, ObjectType objectType, boolean includedDeleted, InternalTenantContext internalTenantContext);
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/TagSqlDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/TagSqlDao.java
index 26caab0..53ebbc5 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/TagSqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagSqlDao.java
@@ -16,6 +16,7 @@
package com.ning.billing.util.tag.dao;
+import java.util.Iterator;
import java.util.List;
import java.util.UUID;
@@ -23,6 +24,8 @@ 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.Define;
+import org.skife.jdbi.v2.sqlobject.customizers.FetchSize;
import com.ning.billing.ObjectType;
import com.ning.billing.callcontext.InternalCallContext;
@@ -50,4 +53,13 @@ public interface TagSqlDao extends EntitySqlDao<TagModelDao, Tag> {
List<TagModelDao> getTagsForObjectIncludedDeleted(@Bind("objectId") UUID objectId,
@Bind("objectType") ObjectType objectType,
@BindBean InternalTenantContext internalTenantContext);
+
+ @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<TagModelDao> searchTags(@Define("searchKey") final String searchKey,
+ @Bind("offset") final Long offset,
+ @Bind("rowCount") final Long rowCount,
+ @BindBean final InternalTenantContext context);
}
diff --git a/util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg
index 1a91d2f..ee146da 100644
--- a/util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg
@@ -39,3 +39,19 @@ and object_type = :objectType
;
>>
+searchCustomFields(searchKey, offset, rowCount) ::= <<
+select SQL_CALC_FOUND_ROWS
+<allTableFields("t.")>
+from <tableName()> t
+where 1 = 1
+and (
+ <idField("t.")> = '<searchKey>'
+ or t.object_type like '%<searchKey>%'
+ or t.field_name like '%<searchKey>%'
+ or t.field_value like '%<searchKey>%'
+)
+<AND_CHECK_TENANT("t.")>
+order by <recordIdField("t.")> ASC
+limit :offset, :rowCount
+;
+>>
diff --git a/util/src/main/resources/com/ning/billing/util/tag/dao/TagSqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/tag/dao/TagSqlDao.sql.stg
index ae38127..7b14556 100644
--- a/util/src/main/resources/com/ning/billing/util/tag/dao/TagSqlDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/tag/dao/TagSqlDao.sql.stg
@@ -57,3 +57,21 @@ and t.object_type = :objectType
<AND_CHECK_TENANT("t.")>
;
>>
+
+searchTags(searchKey, offset, rowCount) ::= <<
+select SQL_CALC_FOUND_ROWS
+<allTableFields("t.")>
+from <tableName()> t
+join tag_definitions td on td.id = t.tag_definition_id
+where 1 = 1
+and (
+ <idField("t.")> = '<searchKey>'
+ or t.object_type like '%<searchKey>%'
+ or td.name like '%<searchKey>%'
+ or td.description like '%<searchKey>%'
+)
+<AND_CHECK_TENANT("t.")>
+order by <recordIdField("t.")> ASC
+limit :offset, :rowCount
+;
+>>
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 8521cb9..fd0c414 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
@@ -24,6 +24,7 @@ import com.ning.billing.ObjectType;
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.Pagination;
import com.ning.billing.util.entity.dao.MockEntityDaoBase;
public class MockCustomFieldDao extends MockEntityDaoBase<CustomFieldModelDao, CustomField, CustomFieldApiException> implements CustomFieldDao {
@@ -49,4 +50,9 @@ public class MockCustomFieldDao extends MockEntityDaoBase<CustomFieldModelDao, C
public List<CustomFieldModelDao> getCustomFieldsForAccount(final InternalTenantContext context) {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public Pagination<CustomFieldModelDao> searchCustomFields(final String searchKey, final Long offset, final Long limit, final InternalTenantContext context) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDao.java b/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDao.java
index 890febb..7c8630d 100644
--- a/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDao.java
+++ b/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDao.java
@@ -27,12 +27,15 @@ import com.ning.billing.ObjectType;
import com.ning.billing.callcontext.InternalCallContext;
import com.ning.billing.callcontext.InternalTenantContext;
import com.ning.billing.util.api.TagApiException;
+import com.ning.billing.util.entity.Pagination;
+import com.ning.billing.util.entity.dao.MockEntityDaoBase;
+import com.ning.billing.util.tag.Tag;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
-public class MockTagDao implements TagDao {
+public class MockTagDao extends MockEntityDaoBase<TagModelDao, Tag, TagApiException> implements TagDao {
private final Map<UUID, List<TagModelDao>> tagStore = new HashMap<UUID, List<TagModelDao>>();
@@ -60,6 +63,11 @@ public class MockTagDao implements TagDao {
}
@Override
+ public Pagination<TagModelDao> searchTags(final String searchKey, final Long offset, final Long limit, final InternalTenantContext context) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public TagModelDao getById(final UUID tagId, final InternalTenantContext context) {
throw new UnsupportedOperationException();
}