killbill-aplcache
Changes
account/pom.xml 2(+1 -1)
api/pom.xml 2(+1 -1)
beatrix/pom.xml 2(+1 -1)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java 139(+139 -0)
catalog/pom.xml 2(+1 -1)
currency/pom.xml 2(+1 -1)
entitlement/pom.xml 2(+1 -1)
invoice/pom.xml 2(+1 -1)
jaxrs/pom.xml 2(+1 -1)
junction/pom.xml 2(+1 -1)
NEWS 6(+6 -0)
overdue/pom.xml 2(+1 -1)
payment/pom.xml 2(+1 -1)
payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java 12(+11 -1)
pom.xml 4(+2 -2)
profiles/killbill/pom.xml 2(+1 -1)
profiles/killpay/pom.xml 2(+1 -1)
profiles/pom.xml 2(+1 -1)
subscription/pom.xml 2(+1 -1)
tenant/pom.xml 2(+1 -1)
usage/pom.xml 2(+1 -1)
util/pom.xml 2(+1 -1)
Details
account/pom.xml 2(+1 -1)
diff --git a/account/pom.xml b/account/pom.xml
index df1ec85..ba0b8a9 100644
--- a/account/pom.xml
+++ b/account/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.17.7-SNAPSHOT</version>
+ <version>0.17.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-account</artifactId>
api/pom.xml 2(+1 -1)
diff --git a/api/pom.xml b/api/pom.xml
index 0435593..8936827 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.17.7-SNAPSHOT</version>
+ <version>0.17.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-internal-api</artifactId>
beatrix/pom.xml 2(+1 -1)
diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index 689180a..c752c0d 100644
--- a/beatrix/pom.xml
+++ b/beatrix/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.17.7-SNAPSHOT</version>
+ <version>0.17.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-beatrix</artifactId>
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java
index ec6768b..8fd9f9d 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java
@@ -19,11 +19,14 @@
package org.killbill.billing.beatrix.integration;
import java.math.BigDecimal;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
+import javax.inject.Inject;
+
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
import org.killbill.billing.account.api.Account;
@@ -37,8 +40,14 @@ import org.killbill.billing.catalog.api.PriceListSet;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.api.DefaultEntitlement;
import org.killbill.billing.entitlement.api.Entitlement.EntitlementActionPolicy;
+import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications;
+import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications.SubscriptionNotification;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceItemType;
+import org.killbill.billing.invoice.api.InvoiceStatus;
+import org.killbill.billing.invoice.dao.InvoiceDao;
+import org.killbill.billing.invoice.dao.InvoiceItemModelDao;
+import org.killbill.billing.invoice.dao.InvoiceModelDao;
import org.killbill.billing.payment.api.Payment;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.payment.api.TransactionStatus;
@@ -52,6 +61,10 @@ import static org.testng.Assert.assertNotNull;
public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
+ @Inject
+ protected InvoiceDao invoiceDao;
+
+
@AfterMethod(groups = "slow")
public void afterMethod() throws Exception {
super.afterMethod();
@@ -607,4 +620,130 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
refundPaymentWithInvoiceItemAdjAndCheckForCompletion(account, payment1, iias, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.INVOICE_ADJUSTMENT);
checkNoMoreInvoiceToGenerate(account);
}
+
+ @Test(groups = "slow")
+ public void testWithSuperflousRepairedItems() throws Exception {
+
+ // We take april as it has 30 days (easier to play with BCD)
+ final LocalDate today = new LocalDate(2012, 4, 1);
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(1));
+
+ // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+ clock.setDay(today);
+
+ final String productName = "Shotgun";
+ final BillingPeriod term = BillingPeriod.MONTHLY;
+ final String pricelistName = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+ //
+ // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE NextEvent.INVOICE
+ //
+
+ final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.INVOICE, NextEvent.BLOCK);
+ assertNotNull(bpEntitlement);
+
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ assertEquals(invoices.size(), 1);
+ ImmutableList<ExpectedInvoiceItemCheck> toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.FIXED, BigDecimal.ZERO));
+ invoiceChecker.checkInvoice(invoices.get(0).getId(), callContext, toBeChecked);
+
+ //
+ // Check we get the first invoice at the phase event
+ //
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ // Move the clock to 2012-05-02
+ clock.addDays(31);
+ assertListenerStatus();
+
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ assertEquals(invoices.size(), 2);
+
+ toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.FIXED, BigDecimal.ZERO));
+ invoiceChecker.checkInvoice(invoices.get(0).getId(), callContext, toBeChecked);
+
+ toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+ final Invoice lastInvoice = invoices.get(1);
+ invoiceChecker.checkInvoice(lastInvoice.getId(), callContext, toBeChecked);
+
+
+ //
+ // Let's add a bunch of items by hand to pretend we have lots of cancelling items that should be cleaned (data issue after a potential invoice bug)
+ //
+ // We test both full and partial repair.
+
+ /*
+ final UUID id, @Nullable final DateTime createdDate, final UUID accountId,
+ @Nullable final Integer invoiceNumber, final LocalDate invoiceDate, final LocalDate targetDate,
+ final Currency currency, final boolean migrated, final InvoiceStatus status, final boolean isParentInvoice
+ */
+ final InvoiceModelDao shellInvoice = new InvoiceModelDao(UUID.randomUUID(), lastInvoice.getCreatedDate(), lastInvoice.getAccountId(), null,
+ lastInvoice.getInvoiceDate(), lastInvoice.getTargetDate(), lastInvoice.getCurrency(), false, InvoiceStatus.COMMITTED, false);
+
+ final InvoiceItemModelDao recurring1 = new InvoiceItemModelDao(lastInvoice.getCreatedDate(), InvoiceItemType.RECURRING, lastInvoice.getId(), lastInvoice.getAccountId(),
+ bpEntitlement.getBundleId(), bpEntitlement.getBaseEntitlementId(), "", "shotgun-monthly", "shotgun-monthly-evergreen",
+ null, new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), new BigDecimal("249.95"), new BigDecimal("249.95"), account.getCurrency(), null);
+
+ final InvoiceItemModelDao repair1 = new InvoiceItemModelDao(lastInvoice.getCreatedDate(), InvoiceItemType.REPAIR_ADJ, lastInvoice.getId(), lastInvoice.getAccountId(),
+ bpEntitlement.getBundleId(), bpEntitlement.getBaseEntitlementId(), null, null, null,
+ null, new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), new BigDecimal("-249.95"), new BigDecimal("-249.95"), account.getCurrency(), recurring1.getId());
+
+ final InvoiceItemModelDao recurring2 = new InvoiceItemModelDao(lastInvoice.getCreatedDate(), InvoiceItemType.RECURRING, lastInvoice.getId(), lastInvoice.getAccountId(),
+ bpEntitlement.getBundleId(), bpEntitlement.getBaseEntitlementId(), "", "shotgun-monthly", "shotgun-monthly-evergreen",
+ null, new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), new BigDecimal("249.95"), new BigDecimal("249.95"), account.getCurrency(), null);
+
+
+ final InvoiceItemModelDao repair21 = new InvoiceItemModelDao(lastInvoice.getCreatedDate(), InvoiceItemType.REPAIR_ADJ, lastInvoice.getId(), lastInvoice.getAccountId(),
+ bpEntitlement.getBundleId(), bpEntitlement.getBaseEntitlementId(), null, null, null,
+ null, new LocalDate(2012, 5, 1), new LocalDate(2012, 5, 13), new BigDecimal("-100.95"), new BigDecimal("-100.95"), account.getCurrency(), recurring2.getId());
+
+ final InvoiceItemModelDao repair22 = new InvoiceItemModelDao(lastInvoice.getCreatedDate(), InvoiceItemType.REPAIR_ADJ, lastInvoice.getId(), lastInvoice.getAccountId(),
+ bpEntitlement.getBundleId(), bpEntitlement.getBaseEntitlementId(), null, null, null,
+ null, new LocalDate(2012, 5, 13), new LocalDate(2012, 5, 22), new BigDecimal("-100"), new BigDecimal("-100"), account.getCurrency(), recurring2.getId());
+
+ final InvoiceItemModelDao repair23 = new InvoiceItemModelDao(lastInvoice.getCreatedDate(), InvoiceItemType.REPAIR_ADJ, lastInvoice.getId(), lastInvoice.getAccountId(),
+ bpEntitlement.getBundleId(), bpEntitlement.getBaseEntitlementId(), null, null, null,
+ null, new LocalDate(2012, 5, 22), new LocalDate(2012, 6, 1), new BigDecimal("-49"), new BigDecimal("-49"), account.getCurrency(), recurring2.getId());
+
+
+
+ final InvoiceItemModelDao recurring3 = new InvoiceItemModelDao(lastInvoice.getCreatedDate(), InvoiceItemType.RECURRING, lastInvoice.getId(), lastInvoice.getAccountId(),
+ bpEntitlement.getBundleId(), bpEntitlement.getBaseEntitlementId(), "", "shotgun-monthly", "shotgun-monthly-evergreen",
+ null, new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), new BigDecimal("249.95"), new BigDecimal("249.95"), account.getCurrency(), null);
+
+
+ final InvoiceItemModelDao repair3 = new InvoiceItemModelDao(lastInvoice.getCreatedDate(), InvoiceItemType.REPAIR_ADJ, lastInvoice.getId(), lastInvoice.getAccountId(),
+ bpEntitlement.getBundleId(), bpEntitlement.getBaseEntitlementId(), null, null, null,
+ null, new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), new BigDecimal("-249.95"), new BigDecimal("-249.95"), account.getCurrency(), recurring3.getId());
+
+ List<InvoiceItemModelDao> newItems = new ArrayList<InvoiceItemModelDao>();
+ newItems.add(recurring1);
+ newItems.add(repair1);
+ newItems.add(recurring2);
+ newItems.add(repair21);
+ newItems.add(repair22);
+ newItems.add(repair23);
+ newItems.add(recurring3);
+ newItems.add(repair3);
+ invoiceDao.createInvoice(shellInvoice, newItems, false, new FutureAccountNotifications(new HashMap<UUID, List<SubscriptionNotification>>()), internalCallContext);
+
+
+ // Move ahead one month, verify nothing from previous data was generated
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ assertEquals(invoices.size(), 3);
+
+ toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 1), new LocalDate(2012, 7, 1), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+ invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, toBeChecked);
+
+
+
+ }
+
}
catalog/pom.xml 2(+1 -1)
diff --git a/catalog/pom.xml b/catalog/pom.xml
index 3a28cf4..49aad72 100644
--- a/catalog/pom.xml
+++ b/catalog/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.17.7-SNAPSHOT</version>
+ <version>0.17.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-catalog</artifactId>
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
index 19ceed6..84fe628 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
@@ -308,8 +308,8 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
@Override
public String toString() {
return "DefaultPlan [name=" + name + ", effectiveDateForExistingSubscriptions="
- + effectiveDateForExistingSubscriptions + ", product=" + product + ", initialPhases="
- + Arrays.toString(initialPhases) + ", finalPhase=" + finalPhase + ", plansAllowedInBundle="
+ + effectiveDateForExistingSubscriptions + ", product=" + product.getName() + ", initialPhases="
+ + Arrays.toString(initialPhases) + ", finalPhase=" + finalPhase.getName() + ", plansAllowedInBundle="
+ plansAllowedInBundle + "]";
}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlanPhase.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlanPhase.java
index ee2226a..bf451af 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlanPhase.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlanPhase.java
@@ -242,4 +242,17 @@ public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implem
//result = 31 * result + (usages != null ? Arrays.hashCode(usages) : 0);
return result;
}
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("DefaultPlanPhase{");
+ sb.append("type=").append(type);
+ sb.append(", duration=").append(duration);
+ sb.append(", fixed=").append(fixed);
+ sb.append(", recurring=").append(recurring);
+ sb.append(", usages=").append(Arrays.toString(usages));
+ sb.append(", plan=").append(plan.getName());
+ sb.append('}');
+ return sb.toString();
+ }
}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java b/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
index fbf5591..83f07ac 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
@@ -36,11 +36,9 @@ import javax.xml.bind.annotation.XmlRootElement;
import org.joda.time.DateTime;
import org.killbill.billing.ErrorCode;
-import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingAlignment;
import org.killbill.billing.catalog.api.BillingMode;
-import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.Catalog;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.Currency;
@@ -131,6 +129,10 @@ public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> impleme
public Plan findPlan(final StandaloneCatalog catalog) throws CatalogApiException {
return catalog.createOrFindCurrentPlan(spec, overrides);
}
+
+ public PlanSpecifier getSpec() {
+ return spec;
+ }
}
private CatalogPlanEntry findCatalogPlanEntry(final PlanRequestWrapper wrapper,
@@ -169,7 +171,12 @@ public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> impleme
}
}
- throw new CatalogApiException(ErrorCode.CAT_NO_CATALOG_FOR_GIVEN_DATE, requestedDate.toDate().toString());
+ final PlanSpecifier spec = wrapper.getSpec();
+ throw new CatalogApiException(ErrorCode.CAT_PLAN_NOT_FOUND,
+ spec.getPlanName() != null ? spec.getPlanName() : "undefined",
+ spec.getProductName() != null ? spec.getProductName() : "undefined",
+ spec.getBillingPeriod() != null ? spec.getBillingPeriod() : "undefined",
+ spec.getPriceListName() != null ? spec.getPriceListName() : "undefined");
}
private static class CatalogPlanEntry {
currency/pom.xml 2(+1 -1)
diff --git a/currency/pom.xml b/currency/pom.xml
index fc2b5a7..3278f26 100644
--- a/currency/pom.xml
+++ b/currency/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.17.7-SNAPSHOT</version>
+ <version>0.17.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-currency</artifactId>
entitlement/pom.xml 2(+1 -1)
diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index 02d02a8..20c9c9b 100644
--- a/entitlement/pom.xml
+++ b/entitlement/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.17.7-SNAPSHOT</version>
+ <version>0.17.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-entitlement</artifactId>
invoice/pom.xml 2(+1 -1)
diff --git a/invoice/pom.xml b/invoice/pom.xml
index ec47467..24b2082 100644
--- a/invoice/pom.xml
+++ b/invoice/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.17.7-SNAPSHOT</version>
+ <version>0.17.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-invoice</artifactId>
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/tree/ItemsNodeInterval.java b/invoice/src/main/java/org/killbill/billing/invoice/tree/ItemsNodeInterval.java
index 48e946a..e3f9af0 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/tree/ItemsNodeInterval.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/tree/ItemsNodeInterval.java
@@ -21,6 +21,7 @@ package org.killbill.billing.invoice.tree;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -282,18 +283,19 @@ public class ItemsNodeInterval extends NodeInterval {
// it has no more leaves and no more items.
// Case B - This is a bit more involved: We look for full repair that happened in pieces; this will translate to an ADD element of a NodeInterval,
// whose children completely map the interval (isPartitionedByChildren) and where each child will have a CANCEL item pointing to the ADD.
- // When we detect such nodes, we delete both the ADD in the parent interval and the CANCEL in the children
+ // When we detect such nodes, we delete both the ADD in the parent interval and the CANCEL in the children (and cleanup the interval if it does not have items)
//
private void pruneTree() {
walkTree(new WalkCallback() {
@Override
public void onCurrentNode(final int depth, final NodeInterval curNode, final NodeInterval parent) {
- if(curNode.isRoot()) {
+ if (curNode.isRoot()) {
return;
}
final ItemsInterval curNodeItems = ((ItemsNodeInterval) curNode).getItemsInterval();
+
// Case A:
final boolean isEmpty = curNodeItems.mergeCancellingPairs();
if (isEmpty && curNode.getLeftChild() == null) {
@@ -305,14 +307,22 @@ public class ItemsNodeInterval extends NodeInterval {
}
// Case B -- look for such case, and if found (foundFullRepairByParts) we fix them below.
- final Iterator<Item> it = curNodeItems.get_ADD_items().iterator();
+ List<Item> curNodeItemsToBeRemoved = null;
+ final Iterator<Item> it = curNodeItems.get_ADD_items().iterator();
+ // For each item on this curNode interval we check if there is a matching set of CANCEL items on the children (resulting in completely cancelling that item).
while (it.hasNext()) {
final Item curAddItem = it.next();
+ //
+ // We already know the children partition fully the 'curNode' interval, we just need to see if for each piece
+ // we find a matching CANCEL item pointing to this 'curAddItem'
+ //
NodeInterval curChild = curNode.getLeftChild();
- Map<ItemsInterval, Item> toBeRemoved = new HashMap<ItemsInterval, Item>();
- boolean foundFullRepairByParts = true;
+ Map<ItemsInterval, Item> childrenCancellingToBeRemoved = new HashMap<ItemsInterval, Item>();
+
+ // Note that because of previous iterations, curChild could now be null so we need to initialize the foundFullRepairByParts based on that new state.
+ boolean foundFullRepairByParts = curChild != null;
while (curChild != null) {
final ItemsInterval curChildItems = ((ItemsNodeInterval) curChild).getItemsInterval();
Item cancellingItem = curChildItems.getCancelledItemIfExists(curAddItem.getId());
@@ -320,20 +330,27 @@ public class ItemsNodeInterval extends NodeInterval {
foundFullRepairByParts = false;
break;
}
- toBeRemoved.put(curChildItems, cancellingItem);
+ childrenCancellingToBeRemoved.put(curChildItems, cancellingItem);
curChild = curChild.getRightSibling();
}
if (foundFullRepairByParts) {
- for (ItemsInterval curItemsInterval : toBeRemoved.keySet()) {
- curItemsInterval.remove(toBeRemoved.get(curItemsInterval));
+ for (ItemsInterval curItemsInterval : childrenCancellingToBeRemoved.keySet()) {
+ curItemsInterval.remove(childrenCancellingToBeRemoved.get(curItemsInterval));
if (curItemsInterval.size() == 0) {
curNode.removeChild(curItemsInterval.getNodeInterval());
}
}
- curNodeItems.remove(curAddItem);
+ if (curNodeItemsToBeRemoved == null) {
+ curNodeItemsToBeRemoved = new ArrayList<Item>();
+ }
+ curNodeItemsToBeRemoved.add(curAddItem);
}
}
+ // Finally Execute the removal of the curNodeItems outside of the upper while loop so as to not trigger ConcurrentModificationException (see #641)
+ for (Item curNodeItemsRemoval : curNodeItemsToBeRemoved) {
+ curNodeItems.remove(curNodeItemsRemoval);
+ }
}
});
}
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tree/TestSubscriptionItemTree.java b/invoice/src/test/java/org/killbill/billing/invoice/tree/TestSubscriptionItemTree.java
index 102f9c7..edc9cf5 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/tree/TestSubscriptionItemTree.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/tree/TestSubscriptionItemTree.java
@@ -394,6 +394,189 @@ public class TestSubscriptionItemTree extends InvoiceTestSuiteNoDB {
verifyResult(tree.getView(), expectedResult);
}
+ // Will test the case A from ItemsNodeInterval#prune logic (when we delete a node while walking the tree)
+ @Test(groups = "fast")
+ public void testFullRepairPruneLogic1() {
+
+ final LocalDate startDate1 = new LocalDate(2015, 1, 1);
+ final LocalDate endDate1 = new LocalDate(2015, 2, 1);
+
+
+ final LocalDate startDate2 = endDate1;
+ final LocalDate endDate2 = new LocalDate(2015, 3, 1);
+
+ final LocalDate startDate3 = endDate2;
+ final LocalDate endDate3 = new LocalDate(2015, 4, 1);
+
+ final BigDecimal monthlyRate = new BigDecimal("12.00");
+ final BigDecimal monthlyAmount = monthlyRate;
+
+ final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate1, endDate1, monthlyAmount, monthlyRate, currency);
+ final InvoiceItem monthly2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate2, endDate2, monthlyAmount, monthlyRate, currency);
+ final InvoiceItem repairMonthly2 = new RepairAdjInvoiceItem(invoiceId, accountId, startDate2, endDate2, new BigDecimal("-12.00"), currency, monthly2.getId());
+ final InvoiceItem monthly3 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate3, endDate3, monthlyAmount, monthlyRate, currency);
+
+ final List<InvoiceItem> expectedResult = Lists.newLinkedList();
+ expectedResult.add(monthly1);
+ expectedResult.add(monthly3);
+
+ final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId, invoiceId);
+ tree.addItem(monthly1);
+ tree.addItem(monthly2);
+ tree.addItem(repairMonthly2);
+ tree.addItem(monthly3);
+
+ tree.build();
+ verifyResult(tree.getView(), expectedResult);
+ }
+
+
+ // Will test the case A from ItemsNodeInterval#prune logic (an item is left on the interval)
+ @Test(groups = "fast")
+ public void testFullRepairPruneLogic2() {
+
+ final LocalDate startDate1 = new LocalDate(2015, 1, 1);
+ final LocalDate endDate1 = new LocalDate(2015, 2, 1);
+
+
+ final LocalDate startDate2 = endDate1;
+ final LocalDate endDate2 = new LocalDate(2015, 3, 1);
+
+ final LocalDate startDate3 = endDate2;
+ final LocalDate endDate3 = new LocalDate(2015, 4, 1);
+
+ final BigDecimal monthlyRateInit = new BigDecimal("12.00");
+ final BigDecimal monthlyAmountInit = monthlyRateInit;
+
+ final BigDecimal monthlyRateFinal = new BigDecimal("15.00");
+ final BigDecimal monthlyAmountFinal = monthlyRateFinal;
+
+ final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate1, endDate1, monthlyRateInit, monthlyAmountInit, currency);
+ final InvoiceItem monthly2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate2, endDate2, monthlyRateInit, monthlyAmountInit, currency);
+ final InvoiceItem repairMonthly2 = new RepairAdjInvoiceItem(invoiceId, accountId, startDate2, endDate2, new BigDecimal("-12.00"), currency, monthly2.getId());
+
+ final InvoiceItem monthly2New = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate2, endDate2, monthlyRateFinal, monthlyAmountFinal, currency);
+
+ final InvoiceItem monthly3 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate3, endDate3, monthlyRateFinal, monthlyAmountFinal, currency);
+
+ final List<InvoiceItem> expectedResult = Lists.newLinkedList();
+ expectedResult.add(monthly1);
+ expectedResult.add(monthly2New);
+ expectedResult.add(monthly3);
+
+ final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId, invoiceId);
+ tree.addItem(monthly1);
+ tree.addItem(monthly2);
+ tree.addItem(repairMonthly2);
+ tree.addItem(monthly2New);
+ tree.addItem(monthly3);
+
+ tree.build();
+ verifyResult(tree.getView(), expectedResult);
+
+ }
+
+
+ // Will test the case B from ItemsNodeInterval#prune logic
+ @Test(groups = "fast")
+ public void testFullRepairByPartsPruneLogic1() {
+
+ final LocalDate startDate = new LocalDate(2015, 2, 1);
+ final LocalDate intermediate1 = new LocalDate(2015, 2, 8);
+ final LocalDate intermediate2 = new LocalDate(2015, 2, 16);
+ final LocalDate intermediate3 = new LocalDate(2015, 2, 24);
+ final LocalDate endDate = new LocalDate(2015, 3, 1);
+
+ final BigDecimal monthlyRate = new BigDecimal("12.00");
+ final BigDecimal monthlyAmount = monthlyRate;
+
+
+ final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+ final InvoiceItem repair11 = new RepairAdjInvoiceItem(invoiceId, accountId, startDate, intermediate1, new BigDecimal("3.00"), currency, monthly1.getId());
+ final InvoiceItem repair12 = new RepairAdjInvoiceItem(invoiceId, accountId, intermediate1, intermediate2, new BigDecimal("3.00"), currency, monthly1.getId());
+ final InvoiceItem repair13 = new RepairAdjInvoiceItem(invoiceId, accountId, intermediate2, intermediate3, new BigDecimal("3.00"), currency, monthly1.getId());
+ final InvoiceItem repair14 = new RepairAdjInvoiceItem(invoiceId, accountId, intermediate3, endDate, new BigDecimal("3.00"), currency, monthly1.getId());
+
+ final InvoiceItem monthly2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+ final InvoiceItem repair21 = new RepairAdjInvoiceItem(invoiceId, accountId, startDate, intermediate1, new BigDecimal("3.00"), currency, monthly2.getId());
+ final InvoiceItem repair22 = new RepairAdjInvoiceItem(invoiceId, accountId, intermediate1, intermediate2, new BigDecimal("3.00"), currency, monthly2.getId());
+ final InvoiceItem repair23 = new RepairAdjInvoiceItem(invoiceId, accountId, intermediate2, intermediate3, new BigDecimal("3.00"), currency, monthly2.getId());
+ final InvoiceItem repair24 = new RepairAdjInvoiceItem(invoiceId, accountId, intermediate3, endDate, new BigDecimal("3.00"), currency, monthly2.getId());
+
+ final InvoiceItem monthly3 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+
+
+ final List<InvoiceItem> expectedResult = Lists.newLinkedList();
+ expectedResult.add(monthly3);
+
+ // First test with items in order
+ final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId, invoiceId);
+ tree.addItem(monthly1);
+ tree.addItem(repair11);
+ tree.addItem(repair12);
+ tree.addItem(repair13);
+ tree.addItem(repair14);
+
+ tree.addItem(monthly2);
+ tree.addItem(repair21);
+ tree.addItem(repair22);
+ tree.addItem(repair23);
+ tree.addItem(repair24);
+
+ tree.addItem(monthly3);
+
+ tree.build();
+ verifyResult(tree.getView(), expectedResult);
+ }
+
+ // Will test the case A and B from ItemsNodeInterval#prune logic
+ @Test(groups = "fast")
+ public void testFullRepairByPartsPruneLogic2() {
+
+ final LocalDate startDate = new LocalDate(2015, 2, 1);
+ final LocalDate intermediate1 = new LocalDate(2015, 2, 8);
+ final LocalDate intermediate2 = new LocalDate(2015, 2, 16);
+ final LocalDate intermediate3 = new LocalDate(2015, 2, 24);
+ final LocalDate endDate = new LocalDate(2015, 3, 1);
+
+ final BigDecimal monthlyRate = new BigDecimal("12.00");
+ final BigDecimal monthlyAmount = monthlyRate;
+
+
+ final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+ final InvoiceItem repair11 = new RepairAdjInvoiceItem(invoiceId, accountId, startDate, endDate, new BigDecimal("-12.00"), currency, monthly1.getId());
+
+ final InvoiceItem monthly2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+ final InvoiceItem repair21 = new RepairAdjInvoiceItem(invoiceId, accountId, startDate, intermediate1, new BigDecimal("3.00"), currency, monthly2.getId());
+ final InvoiceItem repair22 = new RepairAdjInvoiceItem(invoiceId, accountId, intermediate1, intermediate2, new BigDecimal("3.00"), currency, monthly2.getId());
+ final InvoiceItem repair23 = new RepairAdjInvoiceItem(invoiceId, accountId, intermediate2, intermediate3, new BigDecimal("3.00"), currency, monthly2.getId());
+ final InvoiceItem repair24 = new RepairAdjInvoiceItem(invoiceId, accountId, intermediate3, endDate, new BigDecimal("3.00"), currency, monthly2.getId());
+
+ final InvoiceItem monthly3 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+
+
+ final List<InvoiceItem> expectedResult = Lists.newLinkedList();
+ expectedResult.add(monthly3);
+
+ // First test with items in order
+ final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId, invoiceId);
+ tree.addItem(monthly1);
+ tree.addItem(repair11);
+
+ tree.addItem(monthly2);
+ tree.addItem(repair21);
+ tree.addItem(repair22);
+ tree.addItem(repair23);
+ tree.addItem(repair24);
+
+ tree.addItem(monthly3);
+
+ tree.build();
+ verifyResult(tree.getView(), expectedResult);
+ }
+
+
+
@Test(groups = "fast")
public void testMergeWithNoExisting() {
jaxrs/pom.xml 2(+1 -1)
diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml
index 4fc64e3..45a9f4b 100644
--- a/jaxrs/pom.xml
+++ b/jaxrs/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.17.7-SNAPSHOT</version>
+ <version>0.17.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-jaxrs</artifactId>
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
index ef004a4..bbcecc3 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
*
* The Billing Project 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
@@ -415,13 +415,13 @@ public class AccountResource extends JaxRsResourceBase {
final Callable<List<SubscriptionBundle>> bundlesCallable = new Callable<List<SubscriptionBundle>>() {
@Override
public List<SubscriptionBundle> call() throws Exception {
- return subscriptionApi.getSubscriptionBundlesForAccountId(account.getId(), tenantContext);
+ return subscriptionApi.getSubscriptionBundlesForAccountId(accountId, tenantContext);
}
};
final Callable<List<Invoice>> invoicesCallable = new Callable<List<Invoice>>() {
@Override
public List<Invoice> call() throws Exception {
- return invoiceApi.getInvoicesByAccount(account.getId(), false, tenantContext);
+ return invoiceApi.getInvoicesByAccount(accountId, false, tenantContext);
}
};
final Callable<List<InvoicePayment>> invoicePaymentsCallable = new Callable<List<InvoicePayment>>() {
@@ -452,59 +452,76 @@ public class AccountResource extends JaxRsResourceBase {
AccountAuditLogs accountAuditLogs = null;
if (parallel) {
-
final ExecutorService executor = jaxrsExecutors.getJaxrsExecutorService();
final Future<List<SubscriptionBundle>> futureBundlesCallable = executor.submit(bundlesCallable);
final Future<List<Invoice>> futureInvoicesCallable = executor.submit(invoicesCallable);
final Future<List<InvoicePayment>> futureInvoicePaymentsCallable = executor.submit(invoicePaymentsCallable);
final Future<List<Payment>> futurePaymentsCallable = executor.submit(paymentsCallable);
final Future<AccountAuditLogs> futureAuditsCallable = executor.submit(auditsCallable);
-
- try {
- long ini = System.currentTimeMillis();
- do {
- bundles = (bundles == null) ? runCallableAndHandleTimeout(futureBundlesCallable, 100) : bundles;
- invoices = (invoices == null) ? runCallableAndHandleTimeout(futureInvoicesCallable, 100) : invoices;
- invoicePayments = (invoicePayments == null) ? runCallableAndHandleTimeout(futureInvoicePaymentsCallable, 100) : invoicePayments;
- payments = (payments == null) ? runCallableAndHandleTimeout(futurePaymentsCallable, 100) : payments;
- accountAuditLogs = (accountAuditLogs == null) ? runCallableAndHandleTimeout(futureAuditsCallable, 100) : accountAuditLogs;
- } while ((System.currentTimeMillis() - ini < jaxrsConfig.getJaxrsTimeout().getMillis()) &&
- (bundles == null || invoices == null || invoicePayments == null || payments == null || accountAuditLogs == null));
-
- if (bundles == null || invoices == null || invoicePayments == null || payments == null || accountAuditLogs == null) {
- Response.status(Status.SERVICE_UNAVAILABLE).build();
- }
- } catch (InterruptedException e) {
- handleCallableException(e, ImmutableList.<Future>of(futureBundlesCallable, futureInvoicesCallable, futureInvoicePaymentsCallable, futurePaymentsCallable, futureAuditsCallable));
- } catch (ExecutionException e) {
- handleCallableException(e.getCause(), ImmutableList.<Future>of(futureBundlesCallable, futureInvoicesCallable, futureInvoicePaymentsCallable, futurePaymentsCallable, futureAuditsCallable));
+ final ImmutableList<Future> toBeCancelled = ImmutableList.<Future>of(futureBundlesCallable, futureInvoicesCallable, futureInvoicePaymentsCallable, futurePaymentsCallable, futureAuditsCallable);
+ final int timeoutMsec = 100;
+
+ final long ini = System.currentTimeMillis();
+ do {
+ bundles = (bundles == null) ? waitOnFutureAndHandleTimeout("bundles", futureBundlesCallable, timeoutMsec, toBeCancelled) : bundles;
+ invoices = (invoices == null) ? waitOnFutureAndHandleTimeout("invoices", futureInvoicesCallable, timeoutMsec, toBeCancelled) : invoices;
+ invoicePayments = (invoicePayments == null) ? waitOnFutureAndHandleTimeout("invoicePayments", futureInvoicePaymentsCallable, timeoutMsec, toBeCancelled) : invoicePayments;
+ payments = (payments == null) ? waitOnFutureAndHandleTimeout("payments", futurePaymentsCallable, timeoutMsec, toBeCancelled) : payments;
+ accountAuditLogs = (accountAuditLogs == null) ? waitOnFutureAndHandleTimeout("accountAuditLogs", futureAuditsCallable, timeoutMsec, toBeCancelled) : accountAuditLogs;
+ } while ((System.currentTimeMillis() - ini < jaxrsConfig.getJaxrsTimeout().getMillis()) &&
+ (bundles == null || invoices == null || invoicePayments == null || payments == null || accountAuditLogs == null));
+
+ if (bundles == null || invoices == null || invoicePayments == null || payments == null || accountAuditLogs == null) {
+ Response.status(Status.SERVICE_UNAVAILABLE).build();
}
-
} else {
- try {
- invoices = invoicesCallable.call();
- payments = paymentsCallable.call();
- bundles = bundlesCallable.call();
- accountAuditLogs = auditsCallable.call();
- invoicePayments = invoicePaymentsCallable.call();
- } catch (Exception e) {
- handleCallableException(e);
- }
+ invoices = runCallable("invoices", invoicesCallable);
+ payments = runCallable("payments", paymentsCallable);
+ bundles = runCallable("bundles", bundlesCallable);
+ accountAuditLogs = runCallable("accountAuditLogs", auditsCallable);
+ invoicePayments = runCallable("invoicePayments", invoicePaymentsCallable);
}
json = new AccountTimelineJson(account, invoices, payments, invoicePayments, bundles, accountAuditLogs);
return Response.status(Status.OK).entity(json).build();
}
- private <T> T runCallableAndHandleTimeout(final Future<T> future, final long timeoutMsec) throws ExecutionException, InterruptedException {
+ private <T> T waitOnFutureAndHandleTimeout(final String logSuffix, final Future<T> future, final long timeoutMsec, final Iterable<Future> toBeCancelled) throws PaymentApiException, AccountApiException, InvoiceApiException, SubscriptionApiException {
+ try {
+ return waitOnFutureAndHandleTimeout(future, timeoutMsec);
+ } catch (final InterruptedException e) {
+ log.warn("InterruptedException while retrieving {}", logSuffix, e);
+ handleCallableException(e, toBeCancelled);
+ } catch (final ExecutionException e) {
+ log.warn("ExecutionException while retrieving {}", logSuffix, e);
+ handleCallableException(e.getCause(), toBeCancelled);
+ }
+
+ // Never reached
+ return null;
+ }
+
+ private <T> T waitOnFutureAndHandleTimeout(final Future<T> future, final long timeoutMsec) throws ExecutionException, InterruptedException {
try {
return future.get(timeoutMsec, TimeUnit.MILLISECONDS);
- } catch (TimeoutException e) {
+ } catch (final TimeoutException e) {
return null;
}
}
- private void handleCallableException(final Throwable causeOrException, final List<Future> toBeCancelled) throws AccountApiException, SubscriptionApiException, PaymentApiException, InvoiceApiException {
+ private <T> T runCallable(final String logSuffix, final Callable<T> callable) throws PaymentApiException, AccountApiException, InvoiceApiException, SubscriptionApiException {
+ try {
+ return callable.call();
+ } catch (final Exception e) {
+ log.warn("InterruptedException while retrieving {}", logSuffix, e);
+ handleCallableException(e);
+ }
+
+ // Never reached
+ return null;
+ }
+
+ private void handleCallableException(final Throwable causeOrException, final Iterable<Future> toBeCancelled) throws AccountApiException, SubscriptionApiException, PaymentApiException, InvoiceApiException {
for (final Future f : toBeCancelled) {
f.cancel(true);
}
junction/pom.xml 2(+1 -1)
diff --git a/junction/pom.xml b/junction/pom.xml
index 8e003a7..6206cee 100644
--- a/junction/pom.xml
+++ b/junction/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.17.7-SNAPSHOT</version>
+ <version>0.17.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-junction</artifactId>
NEWS 6(+6 -0)
diff --git a/NEWS b/NEWS
index 8bc0c7b..bafdcf0 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,9 @@
+0.17.8
+ See https://github.com/killbill/killbill/releases/tag/killbill-0.17.8
+
+0.17.7
+ See https://github.com/killbill/killbill/releases/tag/killbill-0.17.7
+
0.17.6
See https://github.com/killbill/killbill/releases/tag/killbill-0.17.6
overdue/pom.xml 2(+1 -1)
diff --git a/overdue/pom.xml b/overdue/pom.xml
index 31afc8d..2abb00d 100644
--- a/overdue/pom.xml
+++ b/overdue/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.17.7-SNAPSHOT</version>
+ <version>0.17.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-overdue</artifactId>
payment/pom.xml 2(+1 -1)
diff --git a/payment/pom.xml b/payment/pom.xml
index 29c1b08..07af64e 100644
--- a/payment/pom.xml
+++ b/payment/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.17.7-SNAPSHOT</version>
+ <version>0.17.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-payment</artifactId>
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java
index b83cc69..22e12ff 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PluginControlPaymentProcessor.java
@@ -27,6 +27,7 @@ import javax.inject.Inject;
import org.killbill.automaton.MissingEntryException;
import org.killbill.automaton.State;
+import org.killbill.billing.ErrorCode;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.account.api.AccountApiException;
import org.killbill.billing.account.api.AccountInternalApi;
@@ -47,6 +48,7 @@ import org.killbill.billing.payment.dao.PaymentDao;
import org.killbill.billing.payment.dao.PaymentModelDao;
import org.killbill.billing.payment.dao.PluginPropertySerializer;
import org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException;
+import org.killbill.billing.payment.invoice.InvoicePaymentControlPluginApi;
import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
import org.killbill.billing.tag.TagInternalApi;
import org.killbill.billing.util.callcontext.CallContext;
@@ -289,7 +291,15 @@ public class PluginControlPaymentProcessor extends ProcessorBase {
} catch (final AccountApiException e) {
log.warn("Failed to retry attemptId='{}', paymentControlPlugins='{}'", attemptId, toPluginNamesOnError(paymentControlPluginNames), e);
} catch (final PaymentApiException e) {
- log.warn("Failed to retry attemptId='{}', paymentControlPlugins='{}'", attemptId, toPluginNamesOnError(paymentControlPluginNames), e);
+ // Log exception unless nothing left to be paid
+ if (e.getCode() == ErrorCode.PAYMENT_PLUGIN_API_ABORTED.getCode() &&
+ paymentControlPluginNames != null &&
+ paymentControlPluginNames.size() == 1 &&
+ InvoicePaymentControlPluginApi.PLUGIN_NAME.equals(paymentControlPluginNames.get(0))) {
+ log.warn("Failed to retry attemptId='{}', paymentControlPlugins='{}'. Invoice has already been paid", attemptId, toPluginNamesOnError(paymentControlPluginNames));
+ } else {
+ log.warn("Failed to retry attemptId='{}', paymentControlPlugins='{}'", attemptId, toPluginNamesOnError(paymentControlPluginNames), e);
+ }
} catch (final PluginPropertySerializerException e) {
log.warn("Failed to retry attemptId='{}', paymentControlPlugins='{}'", attemptId, toPluginNamesOnError(paymentControlPluginNames), e);
} catch (final MissingEntryException e) {
pom.xml 4(+2 -2)
diff --git a/pom.xml b/pom.xml
index 7092424..cfa80b9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,10 +21,10 @@
<parent>
<artifactId>killbill-oss-parent</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.135-SNAPSHOT</version>
+ <version>0.136-SNAPSHOT</version>
</parent>
<artifactId>killbill</artifactId>
- <version>0.17.7-SNAPSHOT</version>
+ <version>0.17.9-SNAPSHOT</version>
<packaging>pom</packaging>
<name>killbill</name>
<description>Library for managing recurring subscriptions and the associated billing</description>
profiles/killbill/pom.xml 2(+1 -1)
diff --git a/profiles/killbill/pom.xml b/profiles/killbill/pom.xml
index 2f15884..dda1488 100644
--- a/profiles/killbill/pom.xml
+++ b/profiles/killbill/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>killbill-profiles</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.17.7-SNAPSHOT</version>
+ <version>0.17.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-profiles-killbill</artifactId>
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAccountTimeline.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAccountTimeline.java
index 66eb996..b0a42f3 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAccountTimeline.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAccountTimeline.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
*
* The Billing Project 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
@@ -26,6 +26,8 @@ import javax.annotation.Nullable;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
+import org.killbill.billing.client.KillBillClientException;
+import org.killbill.billing.client.RequestOptions;
import org.killbill.billing.client.model.Account;
import org.killbill.billing.client.model.AccountTimeline;
import org.killbill.billing.client.model.AuditLog;
@@ -42,6 +44,10 @@ import org.killbill.billing.util.audit.ChangeType;
import org.testng.Assert;
import org.testng.annotations.Test;
+import com.google.common.collect.HashMultimap;
+
+import static org.killbill.billing.jaxrs.resources.JaxrsResource.QUERY_PARALLEL;
+
public class TestAccountTimeline extends TestJaxrsBase {
private static final String PAYMENT_REQUEST_PROCESSOR = "PaymentRequestProcessor";
@@ -53,7 +59,7 @@ public class TestAccountTimeline extends TestJaxrsBase {
final Account accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();
- final AccountTimeline timeline = killBillClient.getAccountTimeline(accountJson.getAccountId());
+ final AccountTimeline timeline = getAccountTimeline(accountJson.getAccountId(), AuditLevel.NONE);
Assert.assertEquals(timeline.getPayments().size(), 1);
Assert.assertEquals(timeline.getInvoices().size(), 2);
Assert.assertEquals(timeline.getBundles().size(), 1);
@@ -113,7 +119,7 @@ public class TestAccountTimeline extends TestJaxrsBase {
private void verifyPayments(final UUID accountId, final DateTime startTime, final DateTime endTime,
final BigDecimal refundAmount, final BigDecimal chargebackAmount) throws Exception {
for (final AuditLevel auditLevel : AuditLevel.values()) {
- final AccountTimeline timeline = killBillClient.getAccountTimeline(accountId, auditLevel);
+ final AccountTimeline timeline = getAccountTimeline(accountId, auditLevel);
Assert.assertEquals(timeline.getPayments().size(), 1);
final InvoicePayment payment = timeline.getPayments().get(0);
@@ -183,7 +189,7 @@ public class TestAccountTimeline extends TestJaxrsBase {
private void verifyInvoices(final UUID accountId, final DateTime startTime, final DateTime endTime) throws Exception {
for (final AuditLevel auditLevel : AuditLevel.values()) {
- final AccountTimeline timeline = killBillClient.getAccountTimeline(accountId, auditLevel);
+ final AccountTimeline timeline = getAccountTimeline(accountId, auditLevel);
// Verify invoices
Assert.assertEquals(timeline.getInvoices().size(), 3);
@@ -209,7 +215,7 @@ public class TestAccountTimeline extends TestJaxrsBase {
private void verifyCredits(final UUID accountId, final DateTime startTime, final DateTime endTime, final BigDecimal creditAmount) throws Exception {
for (final AuditLevel auditLevel : AuditLevel.values()) {
- final AccountTimeline timeline = killBillClient.getAccountTimeline(accountId, auditLevel);
+ final AccountTimeline timeline = getAccountTimeline(accountId, auditLevel);
// Verify credits
final List<Credit> credits = timeline.getInvoices().get(1).getCredits();
@@ -229,7 +235,7 @@ public class TestAccountTimeline extends TestJaxrsBase {
private void verifyBundles(final UUID accountId, final DateTime startTime, final DateTime endTime) throws Exception {
for (final AuditLevel auditLevel : AuditLevel.values()) {
- final AccountTimeline timeline = killBillClient.getAccountTimeline(accountId, auditLevel);
+ final AccountTimeline timeline = getAccountTimeline(accountId, auditLevel);
// Verify bundles
Assert.assertEquals(timeline.getBundles().size(), 1);
@@ -298,4 +304,17 @@ public class TestAccountTimeline extends TestJaxrsBase {
Assert.assertEquals(auditLogJson.getComments(), comments);
Assert.assertEquals(auditLogJson.getChangedBy(), changedBy);
}
+
+ private AccountTimeline getAccountTimeline(final UUID accountId, final AuditLevel auditLevel) throws KillBillClientException {
+ final AccountTimeline accountTimeline = killBillClient.getAccountTimeline(accountId, auditLevel, RequestOptions.empty());
+
+ // Verify also the parallel path
+ final HashMultimap<String, String> queryParams = HashMultimap.<String, String>create();
+ queryParams.put(QUERY_PARALLEL, "true");
+ final RequestOptions requestOptions = RequestOptions.builder().withQueryParams(queryParams).build();
+ final AccountTimeline accountTimelineInParallel = killBillClient.getAccountTimeline(accountId, auditLevel, requestOptions);
+ Assert.assertEquals(accountTimelineInParallel, accountTimeline);
+
+ return accountTimeline;
+ }
}
profiles/killpay/pom.xml 2(+1 -1)
diff --git a/profiles/killpay/pom.xml b/profiles/killpay/pom.xml
index d08edaf..87a8077 100644
--- a/profiles/killpay/pom.xml
+++ b/profiles/killpay/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>killbill-profiles</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.17.7-SNAPSHOT</version>
+ <version>0.17.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-profiles-killpay</artifactId>
profiles/pom.xml 2(+1 -1)
diff --git a/profiles/pom.xml b/profiles/pom.xml
index 7978092..22c7a33 100644
--- a/profiles/pom.xml
+++ b/profiles/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.17.7-SNAPSHOT</version>
+ <version>0.17.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-profiles</artifactId>
subscription/pom.xml 2(+1 -1)
diff --git a/subscription/pom.xml b/subscription/pom.xml
index 111ed11..77e04bb 100644
--- a/subscription/pom.xml
+++ b/subscription/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.17.7-SNAPSHOT</version>
+ <version>0.17.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-subscription</artifactId>
tenant/pom.xml 2(+1 -1)
diff --git a/tenant/pom.xml b/tenant/pom.xml
index bfc6e8a..69fe1bf 100644
--- a/tenant/pom.xml
+++ b/tenant/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.17.7-SNAPSHOT</version>
+ <version>0.17.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-tenant</artifactId>
usage/pom.xml 2(+1 -1)
diff --git a/usage/pom.xml b/usage/pom.xml
index 44755bc..0b98838 100644
--- a/usage/pom.xml
+++ b/usage/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.17.7-SNAPSHOT</version>
+ <version>0.17.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-usage</artifactId>
util/pom.xml 2(+1 -1)
diff --git a/util/pom.xml b/util/pom.xml
index 6aa221c..80b2a42 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.17.7-SNAPSHOT</version>
+ <version>0.17.9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-util</artifactId>
diff --git a/util/src/main/java/org/killbill/billing/util/export/dao/CSVExportOutputStream.java b/util/src/main/java/org/killbill/billing/util/export/dao/CSVExportOutputStream.java
index 2b0d14c..154415e 100644
--- a/util/src/main/java/org/killbill/billing/util/export/dao/CSVExportOutputStream.java
+++ b/util/src/main/java/org/killbill/billing/util/export/dao/CSVExportOutputStream.java
@@ -63,6 +63,9 @@ public class CSVExportOutputStream extends OutputStream implements DatabaseExpor
currentTableName = tableName;
final CsvSchema.Builder builder = CsvSchema.builder();
+ // Remove quoting of character which applies (somewhat arbitrarily, Tatu???) for string whose length is greater than MAX_QUOTE_CHECK = 24 -- See CVSWriter#_mayNeedQuotes
+ builder.disableQuoteChar();
+
for (final ColumnInfo columnInfo : columnsForTable) {
builder.addColumn(columnInfo.getColumnName(), getColumnTypeFromSqlType(columnInfo.getDataType()));
}
diff --git a/util/src/main/java/org/killbill/billing/util/export/dao/DatabaseExportDao.java b/util/src/main/java/org/killbill/billing/util/export/dao/DatabaseExportDao.java
index 5fe8093..1f37627 100644
--- a/util/src/main/java/org/killbill/billing/util/export/dao/DatabaseExportDao.java
+++ b/util/src/main/java/org/killbill/billing/util/export/dao/DatabaseExportDao.java
@@ -50,6 +50,8 @@ public class DatabaseExportDao {
private enum TableType {
/* TableName.ACCOUNT */
KB_ACCOUNT("record_id", "tenant_record_id"),
+ /* TableName.ACCOUNT_HISTORY */
+ KB_ACCOUNT_HISTORY("target_record_id", "tenant_record_id"),
/* Any per-account data table */
KB_PER_ACCOUNT("account_record_id", "tenant_record_id"),
/* bus_events, notifications table */
@@ -102,8 +104,15 @@ public class DatabaseExportDao {
private void exportDataForAccountAndTable(final DatabaseExportOutputStream out, final List<ColumnInfo> columnsForTable, final InternalTenantContext context) {
+ TableType tableType = TableType.OTHER;
final String tableName = columnsForTable.get(0).getTableName();
- TableType tableType = TableName.ACCOUNT.getTableName().equals(tableName) ? TableType.KB_ACCOUNT : TableType.OTHER;
+
+ if (TableName.ACCOUNT.getTableName().equals(tableName)) {
+ tableType = TableType.KB_ACCOUNT;
+ } else if (TableName.ACCOUNT_HISTORY.getTableName().equals(tableName)) {
+ tableType = TableType.KB_ACCOUNT_HISTORY;
+ }
+
boolean firstColumn = true;
final StringBuilder queryBuilder = new StringBuilder("select ");
for (final ColumnInfo column : columnsForTable) {
@@ -125,7 +134,7 @@ public class DatabaseExportDao {
}
// Don't export non-account specific tables
- if (tableType == TableType.OTHER) {
+ if (tableType == TableType.OTHER) {
return;
}
diff --git a/util/src/test/java/org/killbill/billing/util/export/dao/TestDatabaseExportDao.java b/util/src/test/java/org/killbill/billing/util/export/dao/TestDatabaseExportDao.java
index 855320f..a4ffe97 100644
--- a/util/src/test/java/org/killbill/billing/util/export/dao/TestDatabaseExportDao.java
+++ b/util/src/test/java/org/killbill/billing/util/export/dao/TestDatabaseExportDao.java
@@ -81,12 +81,13 @@ public class TestDatabaseExportDao extends UtilTestSuiteWithEmbeddedDB {
// Verify new dump
final String newDump = getDump();
Assert.assertEquals(newDump, "-- accounts record_id,id,external_key,email,name,first_name_length,currency,billing_cycle_day_local,payment_method_id,time_zone,locale,address1,address2,company_name,city,state_or_province,country,postal_code,phone,migrated,is_notified_for_invoices,created_date,created_by,updated_date,updated_by,tenant_record_id\n" +
- String.format("%s,\"%s\",,%s,%s,%s,,,,,,,,,,,,,,false,%s,\"%s\",%s,\"%s\",%s,%s", internalCallContext.getAccountRecordId(), accountId, accountEmail, accountName, firstNameLength,
+ String.format("%s,%s,,%s,%s,%s,,,,,,,,,,,,,,false,%s,%s,%s,%s,%s,%s", internalCallContext.getAccountRecordId(), accountId, accountEmail, accountName, firstNameLength,
isNotifiedForInvoices, "1970-05-24T18:33:02.000+0000", createdBy, "1982-02-18T20:03:42.000+0000", updatedBy, internalCallContext.getTenantRecordId()) + "\n" +
"-- " + tableNameA + " record_id,a_column,account_record_id,tenant_record_id\n" +
"1,a," + internalCallContext.getAccountRecordId() + "," + internalCallContext.getTenantRecordId() + "\n" +
"-- " + tableNameB + " record_id,b_column,account_record_id,tenant_record_id\n" +
"1,b," + internalCallContext.getAccountRecordId() + "," + internalCallContext.getTenantRecordId() + "\n");
+
}
private String getDump() {