killbill-memoizeit
Changes
catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalogWithPriceOverride.java 33(+27 -6)
catalog/src/test/java/org/killbill/billing/catalog/TestStandaloneCatalogWithPriceOverride.java 51(+49 -2)
catalog/src/test/resources/catalogTest.xml 34(+32 -2)
entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java 7(+4 -3)
entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java 40(+35 -5)
Details
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalogWithPriceOverride.java b/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalogWithPriceOverride.java
index f78480d..4729e6d 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalogWithPriceOverride.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalogWithPriceOverride.java
@@ -19,6 +19,7 @@ package org.killbill.billing.catalog;
import java.util.regex.Matcher;
+import org.killbill.billing.ErrorCode;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.CatalogApiException;
@@ -68,7 +69,6 @@ public class StandaloneCatalogWithPriceOverride extends StandaloneCatalog implem
return internalCallContextFactory;
}
-
@Override
public Plan createOrFindCurrentPlan(final PlanSpecifier spec, final PlanPhasePriceOverridesWithCallContext overrides) throws CatalogApiException {
final Plan defaultPlan = super.createOrFindCurrentPlan(spec, null);
@@ -86,8 +86,10 @@ public class StandaloneCatalogWithPriceOverride extends StandaloneCatalog implem
public DefaultPlan findCurrentPlan(final String planName) throws CatalogApiException {
final Matcher m = DefaultPriceOverride.CUSTOM_PLAN_NAME_PATTERN.matcher(planName);
if (m.matches()) {
- final InternalTenantContext internalTenantContext = createInternalTenantContext();
- return priceOverride.getOverriddenPlan(planName, this, internalTenantContext);
+ final DefaultPlan plan = maybeGetOverriddenPlan(planName);
+ if (plan != null) {
+ return plan;
+ }
}
return super.findCurrentPlan(planName);
}
@@ -102,13 +104,32 @@ public class StandaloneCatalogWithPriceOverride extends StandaloneCatalog implem
final String planName = DefaultPlanPhase.planName(phaseName);
final Matcher m = DefaultPriceOverride.CUSTOM_PLAN_NAME_PATTERN.matcher(planName);
if (m.matches()) {
- final InternalTenantContext internalTenantContext = createInternalTenantContext();
- final Plan plan = priceOverride.getOverriddenPlan(planName, this, internalTenantContext);
- return plan.findPhase(phaseName);
+ final DefaultPlan plan = maybeGetOverriddenPlan(planName);
+ if (plan != null) {
+ return plan.findPhase(phaseName);
+ }
}
return super.findCurrentPhase(phaseName);
}
+ private DefaultPlan maybeGetOverriddenPlan(final String planName) throws CatalogApiException {
+ final InternalTenantContext internalTenantContext = createInternalTenantContext();
+
+ try {
+ return priceOverride.getOverriddenPlan(planName, this, internalTenantContext);
+ } catch (final RuntimeException e) {
+ if (e.getCause() == null ||
+ e.getCause().getCause() == null ||
+ !(e.getCause().getCause() instanceof CatalogApiException) ||
+ ((CatalogApiException) e.getCause().getCause()).getCode() != ErrorCode.CAT_NO_SUCH_PLAN.getCode()) {
+ throw e;
+ } else {
+ // Otherwise, ambiguous name? See https://github.com/killbill/killbill/issues/842.
+ return null;
+ }
+ }
+ }
+
private InternalTenantContext createInternalTenantContext() {
return internalCallContextFactory.createInternalTenantContext(tenantRecordId, null);
}
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/TestCatalogUpdater.java b/catalog/src/test/java/org/killbill/billing/catalog/TestCatalogUpdater.java
index 585e2b0..48feb2b 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/TestCatalogUpdater.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/TestCatalogUpdater.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -31,10 +31,12 @@ import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.catalog.api.MutableStaticCatalog;
import org.killbill.billing.catalog.api.PhaseType;
import org.killbill.billing.catalog.api.Plan;
+import org.killbill.billing.catalog.api.PlanPhase;
import org.killbill.billing.catalog.api.PriceList;
import org.killbill.billing.catalog.api.Product;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.catalog.api.SimplePlanDescriptor;
+import org.killbill.billing.catalog.api.StaticCatalog;
import org.killbill.billing.catalog.api.TimeUnit;
import org.killbill.billing.catalog.api.user.DefaultSimplePlanDescriptor;
import org.killbill.xmlloader.XMLLoader;
@@ -62,6 +64,37 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
assertEquals(catalog.getCurrentPlans().size(), 0);
}
+ @Test(groups = "fast", description = "https://github.com/killbill/killbill/issues/842")
+ public void testCreateAmbiguousPlan() throws CatalogApiException {
+ final DateTime now = clock.getUTCNow();
+ final SimplePlanDescriptor desc = new DefaultSimplePlanDescriptor("foo-monthly-12345", "Foo", ProductCategory.BASE, Currency.EUR, BigDecimal.TEN, BillingPeriod.MONTHLY, 0, TimeUnit.UNLIMITED, ImmutableList.<String>of());
+
+ final CatalogUpdater catalogUpdater = new CatalogUpdater(BillingMode.IN_ADVANCE, now, desc.getCurrency());
+ catalogUpdater.addSimplePlanDescriptor(desc);
+ final StandaloneCatalog catalog = catalogUpdater.getCatalog();
+
+ assertEquals(catalog.getCurrentPlans().size(), 1);
+
+ final StaticCatalog standaloneCatalogWithPriceOverride = new StandaloneCatalogWithPriceOverride(catalog,
+ priceOverride,
+ internalCallContext.getTenantRecordId(),
+ internalCallContextFactory);
+
+ final Plan plan = catalog.findCurrentPlan("foo-monthly-12345");
+ assertEquals(plan.getName(), "foo-monthly-12345");
+
+ // Verify PriceOverride logic
+ final Plan plan2 = standaloneCatalogWithPriceOverride.findCurrentPlan("foo-monthly-12345");
+ assertEquals(plan2.getName(), "foo-monthly-12345");
+
+ final PlanPhase planPhase = catalog.findCurrentPhase("foo-monthly-12345-evergreen");
+ assertEquals(planPhase.getName(), "foo-monthly-12345-evergreen");
+
+ // Verify PriceOverride logic
+ final PlanPhase phase2 = standaloneCatalogWithPriceOverride.findCurrentPhase("foo-monthly-12345-evergreen");
+ assertEquals(phase2.getName(), "foo-monthly-12345-evergreen");
+ }
+
@Test(groups = "fast")
public void testAddNoTrialPlanOnFirstCatalog() throws CatalogApiException {
@@ -102,7 +135,6 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
assertEquals(priceList.getPlans().iterator().next().getName(), "foo-monthly");
}
-
@Test(groups = "fast")
public void testAddTrialPlanOnFirstCatalog() throws CatalogApiException {
@@ -149,8 +181,6 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
assertEquals(priceList.getPlans().iterator().next().getName(), "foo-monthly");
}
-
-
@Test(groups = "fast")
public void testAddPlanOnExistingCatalog() throws Exception {
@@ -185,8 +215,6 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
assertEquals(priceList.getPlans().size(), 4);
}
-
-
@Test(groups = "fast")
public void testAddExistingPlanWithNewCurrency() throws Exception {
final StandaloneCatalog originalCatalog = XMLLoader.getObjectFromString(Resources.getResource("SpyCarBasic.xml").toExternalForm(), StandaloneCatalog.class);
@@ -231,7 +259,6 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
SimplePlanDescriptor desc = new DefaultSimplePlanDescriptor("standard-monthly", "Standard", ProductCategory.BASE, Currency.EUR, BigDecimal.TEN, BillingPeriod.MONTHLY, 0, TimeUnit.DAYS, ImmutableList.<String>of());
addBadSimplePlanDescriptor(catalogUpdater, desc);
-
// Existing Plan has a 30 days trial => try different trial length
desc = new DefaultSimplePlanDescriptor("standard-monthly", "Standard", ProductCategory.BASE, Currency.EUR, BigDecimal.TEN, BillingPeriod.MONTHLY, 14, TimeUnit.DAYS, ImmutableList.<String>of());
addBadSimplePlanDescriptor(catalogUpdater, desc);
@@ -257,11 +284,9 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
addBadSimplePlanDescriptor(catalogUpdater, desc);
}
-
@Test(groups = "fast")
public void testPlanWithNonFinalFixedTermPhase() throws Exception {
-
final StandaloneCatalog catalog = XMLLoader.getObjectFromString(Resources.getResource("SpyCarBasic.xml").toExternalForm(), StandaloneCatalog.class);
final MutableStaticCatalog mutableCatalog = new DefaultMutableStaticCatalog(catalog);
@@ -272,7 +297,6 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
newProduct.initialize((StandaloneCatalog) mutableCatalog, null);
mutableCatalog.addProduct(newProduct);
-
final DefaultPlanPhase trialPhase = new DefaultPlanPhase();
trialPhase.setPhaseType(PhaseType.TRIAL);
trialPhase.setDuration(new DefaultDuration().setUnit(TimeUnit.DAYS).setNumber(14));
@@ -284,24 +308,22 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
fixedTermPhase.setDuration(new DefaultDuration().setUnit(TimeUnit.MONTHS).setNumber(3));
fixedTermPhase.setRecurring(new DefaultRecurring().setBillingPeriod(BillingPeriod.MONTHLY).setRecurringPrice(new DefaultInternationalPrice().setPrices(new DefaultPrice[]{new DefaultPrice().setCurrency(Currency.USD).setValue(BigDecimal.TEN)})));
-
final DefaultPlanPhase evergreenPhase = new DefaultPlanPhase();
evergreenPhase.setPhaseType(PhaseType.EVERGREEN);
evergreenPhase.setDuration(new DefaultDuration().setUnit(TimeUnit.MONTHS).setNumber(1));
evergreenPhase.setRecurring(new DefaultRecurring().setBillingPeriod(BillingPeriod.MONTHLY).setRecurringPrice(new DefaultInternationalPrice().setPrices(new DefaultPrice[]{new DefaultPrice().setCurrency(Currency.USD).setValue(BigDecimal.TEN)})));
-
final DefaultPlan newPlan = new DefaultPlan();
newPlan.setName("something-with-fixed-term");
newPlan.setPriceListName(DefaultPriceListSet.DEFAULT_PRICELIST_NAME);
newPlan.setProduct(newProduct);
- newPlan.setInitialPhases(new DefaultPlanPhase[] {trialPhase, fixedTermPhase});
+ newPlan.setInitialPhases(new DefaultPlanPhase[]{trialPhase, fixedTermPhase});
newPlan.setFinalPhase(fixedTermPhase);
mutableCatalog.addPlan(newPlan);
newPlan.initialize((StandaloneCatalog) mutableCatalog, new URI("dummy"));
final String newCatalogStr = XMLWriter.writeXML((StandaloneCatalog) mutableCatalog, StandaloneCatalog.class);
- final StandaloneCatalog newCatalog = XMLLoader.getObjectFromStream(new URI("dummy"), new ByteArrayInputStream(newCatalogStr.getBytes(Charset.forName("UTF-8"))), StandaloneCatalog.class);
+ final StandaloneCatalog newCatalog = XMLLoader.getObjectFromStream(new URI("dummy"), new ByteArrayInputStream(newCatalogStr.getBytes(Charset.forName("UTF-8"))), StandaloneCatalog.class);
final DefaultPlan targetPlan = newCatalog.findCurrentPlan("something-with-fixed-term");
Assert.assertEquals(targetPlan.getInitialPhases().length, 2);
@@ -309,8 +331,6 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
}
-
-
@Test(groups = "fast")
public void testVerifyXML() throws Exception {
@@ -559,7 +579,6 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
System.err.println(catalogUpdater.getCatalogXML());
}
-
private StandaloneCatalog enhanceOriginalCatalogForInvalidTestCases(final String catalogName) throws Exception {
final StandaloneCatalog catalog = XMLLoader.getObjectFromString(Resources.getResource(catalogName).toExternalForm(), StandaloneCatalog.class);
@@ -592,7 +611,6 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
mutableCatalog.addPlan(newPlan1);
newPlan1.initialize((StandaloneCatalog) mutableCatalog, new URI("dummy"));
-
final DefaultProduct newProduct2 = new DefaultProduct();
newProduct2.setName("SuperDynamic");
newProduct2.setCatagory(ProductCategory.BASE);
@@ -605,7 +623,6 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
fixedterm2.setDuration(new DefaultDuration().setUnit(TimeUnit.MONTHS).setNumber(3));
fixedterm2.setRecurring(new DefaultRecurring().setBillingPeriod(BillingPeriod.MONTHLY).setRecurringPrice(new DefaultInternationalPrice().setPrices(new DefaultPrice[]{new DefaultPrice().setCurrency(Currency.USD).setValue(BigDecimal.TEN)})));
-
final DefaultPlan newPlan2 = new DefaultPlan();
newPlan2.setName("superdynamic-fixedterm");
newPlan2.setPriceListName(DefaultPriceListSet.DEFAULT_PRICELIST_NAME);
@@ -614,12 +631,10 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
mutableCatalog.addPlan(newPlan2);
newPlan2.initialize((StandaloneCatalog) mutableCatalog, new URI("dummy"));
-
final String newCatalogStr = XMLWriter.writeXML((StandaloneCatalog) mutableCatalog, StandaloneCatalog.class);
return XMLLoader.getObjectFromStream(new URI("dummy"), new ByteArrayInputStream(newCatalogStr.getBytes(Charset.forName("UTF-8"))), StandaloneCatalog.class);
}
-
private void addBadSimplePlanDescriptor(final CatalogUpdater catalogUpdater, final SimplePlanDescriptor desc) {
try {
catalogUpdater.addSimplePlanDescriptor(desc);
@@ -628,4 +643,4 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
assertEquals(e.getCode(), ErrorCode.CAT_FAILED_SIMPLE_PLAN_VALIDATION.getCode());
}
}
-}
\ No newline at end of file
+}
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/TestStandaloneCatalogWithPriceOverride.java b/catalog/src/test/java/org/killbill/billing/catalog/TestStandaloneCatalogWithPriceOverride.java
index 2a70ca6..218aeda 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/TestStandaloneCatalogWithPriceOverride.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/TestStandaloneCatalogWithPriceOverride.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -17,19 +17,66 @@
package org.killbill.billing.catalog;
+import java.math.BigDecimal;
+import java.util.regex.Matcher;
+
import org.killbill.billing.ErrorCode;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.catalog.api.Plan;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverridesWithCallContext;
import org.killbill.billing.catalog.api.PlanSpecifier;
import org.killbill.billing.catalog.api.StaticCatalog;
+import org.killbill.billing.catalog.override.DefaultPriceOverride;
import org.killbill.xmlloader.XMLLoader;
+import org.mockito.Mockito;
import org.testng.Assert;
import org.testng.annotations.Test;
+import com.google.common.collect.ImmutableList;
import com.google.common.io.Resources;
public class TestStandaloneCatalogWithPriceOverride extends CatalogTestSuiteWithEmbeddedDB {
+ @Test(groups = "slow", description = "https://github.com/killbill/killbill/issues/842")
+ public void testCreateAmbiguousPlan() throws Exception {
+ final StandaloneCatalog catalog = XMLLoader.getObjectFromString(Resources.getResource("SpyCarAdvanced.xml").toExternalForm(), StandaloneCatalog.class);
+ final StaticCatalog standaloneCatalogWithPriceOverride = new StandaloneCatalogWithPriceOverride(catalog,
+ priceOverride,
+ internalCallContext.getTenantRecordId(),
+ internalCallContextFactory);
+
+ // Create ambiguous plan name
+ final PlanSpecifier spec = new PlanSpecifier("standard-monthly-67890");
+ final PlanPhasePriceOverridesWithCallContext overrides = Mockito.mock(PlanPhasePriceOverridesWithCallContext.class);
+ Mockito.when(overrides.getCallContext()).thenReturn(callContext);
+ final PlanPhasePriceOverride override = new DefaultPlanPhasePriceOverride("standard-monthly-evergreen", Currency.USD, null, BigDecimal.ONE);
+ Mockito.when(overrides.getOverrides()).thenReturn(ImmutableList.of(override));
+ final Plan plan = standaloneCatalogWithPriceOverride.createOrFindCurrentPlan(spec, overrides);
+ Assert.assertTrue(plan.getName().startsWith("standard-monthly-67890-"));
+ final Matcher m = DefaultPriceOverride.CUSTOM_PLAN_NAME_PATTERN.matcher(plan.getName());
+ Assert.assertTrue(m.matches());
+
+ // From the catalog
+ Assert.assertNotNull(catalog.findCurrentPlan("standard-monthly"));
+ Assert.assertNotNull(standaloneCatalogWithPriceOverride.findCurrentPlan("standard-monthly"));
+
+ // Created on the fly
+ try {
+ catalog.findCurrentPlan(plan.getName());
+ Assert.fail();
+ } catch (final CatalogApiException e) {
+ Assert.assertEquals(e.getCode(), ErrorCode.CAT_NO_SUCH_PLAN.getCode());
+ }
+ Assert.assertNotNull(standaloneCatalogWithPriceOverride.findCurrentPlan("standard-monthly-1"));
+
+ // From the catalog
+ Assert.assertNotNull(catalog.findCurrentPlan("standard-monthly-12345"));
+ Assert.assertNotNull(standaloneCatalogWithPriceOverride.findCurrentPlan("standard-monthly-12345"));
+ }
+
@Test(groups = "slow")
public void testCreatePlanNoProduct() throws Exception {
final StandaloneCatalog catalog = XMLLoader.getObjectFromString(Resources.getResource("SpyCarAdvanced.xml").toExternalForm(), StandaloneCatalog.class);
catalog/src/test/resources/catalogTest.xml 34(+32 -2)
diff --git a/catalog/src/test/resources/catalogTest.xml b/catalog/src/test/resources/catalogTest.xml
index f8d9f93..e4969b1 100644
--- a/catalog/src/test/resources/catalogTest.xml
+++ b/catalog/src/test/resources/catalogTest.xml
@@ -1,8 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
~ Copyright 2010-2013 Ning, Inc.
+ ~ Copyright 2014-2018 Groupon, Inc
+ ~ Copyright 2014-2018 The Billing Project, LLC
~
- ~ Ning licenses this file to you under the Apache License, version 2.0
+ ~ 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
~ License. You may obtain a copy of the License at:
~
@@ -42,6 +44,9 @@
</units>
<products>
+ <product name="Knife">
+ <category>STANDALONE</category>
+ </product>
<product name="Blowdart">
<category>BASE</category>
</product>
@@ -191,6 +196,31 @@
</rules>
<plans>
+ <plan name="knife-monthly-notrial">
+ <product>Knife</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>29.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>29.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>29.95</value>
+ </price>
+ </recurringPrice>
+ </recurring>
+ </finalPhase>
+ </plan>
<plan name="blowdart-monthly-notrial">
<product>Blowdart</product>
<finalPhase type="EVERGREEN">
@@ -216,7 +246,6 @@
</recurring>
</finalPhase>
</plan>
-
<plan name="pistol-monthly-notrial">
<product>Pistol</product>
<finalPhase type="EVERGREEN">
@@ -1373,6 +1402,7 @@
</childPriceList>
<childPriceList name="notrial">
<plans>
+ <plan>knife-monthly-notrial</plan>
<plan>blowdart-monthly-notrial</plan>
<plan>pistol-monthly-notrial</plan>
</plans>
diff --git a/catalog/src/test/resources/SpyCarAdvanced.xml b/catalog/src/test/resources/SpyCarAdvanced.xml
index a08c425..bfc21c4 100644
--- a/catalog/src/test/resources/SpyCarAdvanced.xml
+++ b/catalog/src/test/resources/SpyCarAdvanced.xml
@@ -247,6 +247,39 @@
</recurring>
</finalPhase>
</plan>
+ <plan name="standard-monthly-12345">
+ <product>Standard</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>GBP</currency>
+ <value>75.00</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>85.00</value>
+ </price>
+ <price>
+ <currency>USD</currency>
+ <value>100.00</value>
+ </price>
+ <price>
+ <currency>JPY</currency>
+ <value>10.00</value>
+ </price>
+ <price>
+ <currency>BTC</currency>
+ <value>0.1</value>
+ </price>
+ </recurringPrice>
+ </recurring>
+ </finalPhase>
+ </plan>
<plan name="sports-monthly">
<product>Sports</product>
<initialPhases>
@@ -967,5 +1000,10 @@
<plan>cia-sports-monthly</plan>
</plans>
</childPriceList>
+ <childPriceList name="ambiguous">
+ <plans>
+ <plan>standard-monthly-12345</plan>
+ </plans>
+ </childPriceList>
</priceLists>
</catalog>
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
index 7c84dac..878f1a0 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -111,7 +111,8 @@ public class DefaultEventsStream implements EventsStream {
@Nullable final SubscriptionBaseBundle bundle,
@Nullable final SubscriptionBase baseSubscription,
@Nullable final SubscriptionBase subscription) {
- for (final Object object : new Object[]{account, bundle, baseSubscription, subscription}) {
+ // baseSubscription can be null for STANDALONE products (https://github.com/killbill/killbill/issues/840)
+ for (final Object object : new Object[]{account, bundle, subscription}) {
Preconditions.checkNotNull(object,
"accountId='%s', bundleId='%s', baseSubscriptionId='%s', subscriptionId='%s'",
account != null ? account.getId() : null,
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java
index c5cc731..a3175f3 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java
@@ -24,13 +24,11 @@ import org.killbill.billing.ErrorCode;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.account.api.AccountApiException;
import org.killbill.billing.api.TestApiListener.NextEvent;
-import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.PlanSpecifier;
import org.killbill.billing.catalog.api.PriceListSet;
-import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.EntitlementTestSuiteWithEmbeddedDB;
import org.killbill.billing.entitlement.api.Entitlement.EntitlementActionPolicy;
import org.killbill.billing.entitlement.api.Entitlement.EntitlementSourceType;
@@ -140,6 +138,39 @@ public class TestDefaultEntitlement extends EntitlementTestSuiteWithEmbeddedDB {
assertEquals(entitlement3.getState(), EntitlementState.ACTIVE);
}
+ @Test(groups = "slow", description = "https://github.com/killbill/killbill/issues/840")
+ public void testUncancelEntitlementFor_STANDALONE_Product() throws AccountApiException, EntitlementApiException {
+ final LocalDate initialDate = new LocalDate(2013, 8, 7);
+ clock.setDay(initialDate);
+
+ final Account account = createAccount(getAccountData(7));
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Knife", BillingPeriod.MONTHLY, "notrial", null);
+
+ // Create entitlement and check each field
+ testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+ assertEquals(entitlement.getState(), EntitlementState.ACTIVE);
+
+ clock.addDays(5);
+
+ final LocalDate cancelDate = new LocalDate(clock.getUTCToday().plusDays(1));
+ entitlement.cancelEntitlementWithDate(cancelDate, true, ImmutableList.<PluginProperty>of(), callContext);
+
+ final Entitlement entitlement2 = entitlementApi.getEntitlementForId(entitlement.getId(), callContext);
+ assertEquals(entitlement2.getState(), EntitlementState.ACTIVE);
+ assertEquals(entitlement2.getEffectiveEndDate(), cancelDate);
+
+ testListener.pushExpectedEvents(NextEvent.UNCANCEL);
+ entitlement2.uncancelEntitlement(ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ clock.addDays(1);
+ final Entitlement entitlement3 = entitlementApi.getEntitlementForId(entitlement.getId(), callContext);
+ assertEquals(entitlement3.getState(), EntitlementState.ACTIVE);
+ }
+
@Test(groups = "slow")
public void testCancelWithEntitlementPolicyEOTAndNOCTD() throws AccountApiException, EntitlementApiException, SubscriptionApiException {
final LocalDate initialDate = new LocalDate(2013, 8, 7);
@@ -283,11 +314,10 @@ public class TestDefaultEntitlement extends EntitlementTestSuiteWithEmbeddedDB {
}
@Test(groups = "slow")
- public void testEntitlementChangePlanOnPendingEntitlement() throws AccountApiException, EntitlementApiException {
+ public void testEntitlementChangePlanOnPendingEntitlement() throws AccountApiException, EntitlementApiException {
final LocalDate initialDate = new LocalDate(2013, 8, 7);
clock.setDay(initialDate);
-
final LocalDate startDate = initialDate.plusDays(10);
final Account account = accountApi.createAccount(getAccountData(7), callContext);
@@ -316,7 +346,7 @@ public class TestDefaultEntitlement extends EntitlementTestSuiteWithEmbeddedDB {
entitlement.changePlanWithDate(spec2, ImmutableList.<PlanPhasePriceOverride>of(), startDate, ImmutableList.<PluginProperty>of(), callContext);
- testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
+ testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
clock.addDays(10);
assertListenerStatus();
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AdminResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AdminResource.java
index f85946b..6e84d68 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AdminResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AdminResource.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -17,6 +17,7 @@
package org.killbill.billing.jaxrs.resources;
+import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
@@ -300,8 +301,13 @@ public class AdminResource extends JaxRsResourceBase {
generator.close();
} finally {
// In case the client goes away (IOException), make sure to close the underlying DB connection
- while (iterator.hasNext()) {
- iterator.next();
+ if (tags instanceof Closeable) {
+ ((Closeable) tags).close();
+ } else {
+ // TODO 0.20.x (https://github.com/killbill/killbill/issues/558)
+ while (iterator.hasNext()) {
+ iterator.next();
+ }
}
}
}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
index 0fc62db..8a45b83 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -18,6 +18,7 @@
package org.killbill.billing.jaxrs.resources;
+import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
@@ -349,8 +350,13 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
generator.close();
} finally {
// In case the client goes away (IOException), make sure to close the underlying DB connection
- while (iterator.hasNext()) {
- iterator.next();
+ if (entities instanceof Closeable) {
+ ((Closeable) entities).close();
+ } else {
+ // TODO 0.20.x (https://github.com/killbill/killbill/issues/558)
+ while (iterator.hasNext()) {
+ iterator.next();
+ }
}
}
}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/EhCacheBasedCacheController.java b/util/src/main/java/org/killbill/billing/util/cache/EhCacheBasedCacheController.java
index 7f059fc..fcb43c1 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/EhCacheBasedCacheController.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/EhCacheBasedCacheController.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2012 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -128,7 +128,8 @@ public class EhCacheBasedCacheController<K, V> implements CacheController<K, V>
try {
value = baseCacheLoader.compute(key, cacheLoaderArgument);
} catch (final Exception e) {
- logger.warn("Unable to compute cached value for key='{}' and cacheLoaderArgument='{}'", key, cacheLoaderArgument, e);
+ // Remove noisy log (might be expected, see https://github.com/killbill/killbill/issues/842)
+ //logger.warn("Unable to compute cached value for key='{}' and cacheLoaderArgument='{}'", key, cacheLoaderArgument, e);
throw new RuntimeException(e);
}
diff --git a/util/src/main/java/org/killbill/billing/util/entity/dao/DefaultPaginationSqlDaoHelper.java b/util/src/main/java/org/killbill/billing/util/entity/dao/DefaultPaginationSqlDaoHelper.java
index 574d3b0..d359a19 100644
--- a/util/src/main/java/org/killbill/billing/util/entity/dao/DefaultPaginationSqlDaoHelper.java
+++ b/util/src/main/java/org/killbill/billing/util/entity/dao/DefaultPaginationSqlDaoHelper.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2014 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -18,6 +18,8 @@
package org.killbill.billing.util.entity.dao;
+import java.io.Closeable;
+import java.io.IOException;
import java.util.Iterator;
import javax.annotation.Nullable;
@@ -26,9 +28,13 @@ import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.util.entity.DefaultPagination;
import org.killbill.billing.util.entity.Entity;
import org.killbill.billing.util.entity.Pagination;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class DefaultPaginationSqlDaoHelper {
+ private static final Logger logger = LoggerFactory.getLogger(DefaultPaginationSqlDaoHelper.class);
+
// Number large enough so that small installations have access to an accurate count
// but small enough to not impact very large deployments
// TODO Should this be configurable per tenant?
@@ -66,14 +72,35 @@ public class DefaultPaginationSqlDaoHelper {
// 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.onDemandForStreamingResults(sqlDaoClazz);
- // The count to get maxNbRecords can be expensive on very large datasets. As a heuristic to check how large that number is,
- // we retrieve 1 record at offset SIMPLE_PAGINATION_THRESHOLD (pretty fast). If we've found a record, that means the count is larger
- // than this threshold and we don't issue the full count query
final Long maxNbRecords;
- if (context == null || paginationIteratorBuilder.build((S) sqlDao, SIMPLE_PAGINATION_THRESHOLD, 1L, ordering, context).hasNext()) {
+ if (context == null) {
maxNbRecords = null;
} else {
- maxNbRecords = sqlDao.getCount(context);
+ // The count to get maxNbRecords can be expensive on very large datasets. As a heuristic to check how large that number is,
+ // we retrieve 1 record at offset SIMPLE_PAGINATION_THRESHOLD (pretty fast). If we've found a record, that means the count is larger
+ // than this threshold and we don't issue the full count query
+ final Iterator<M> simplePaginationIterator = paginationIteratorBuilder.build((S) sqlDao, SIMPLE_PAGINATION_THRESHOLD, 1L, ordering, context);
+ final boolean veryLargeDataSet = simplePaginationIterator.hasNext();
+
+ // Make sure to free resources (https://github.com/killbill/killbill/issues/853)
+ if (simplePaginationIterator instanceof Closeable) {
+ // Always the case with the current implementation (simplePaginationIterator is a org.skife.jdbi.v2.ResultIterator)
+ try {
+ ((Closeable) simplePaginationIterator).close();
+ } catch (final IOException e) {
+ logger.warn("Unable to close iterator", e);
+ }
+ } else {
+ while (simplePaginationIterator.hasNext()) {
+ simplePaginationIterator.next();
+ }
+ }
+
+ if (veryLargeDataSet) {
+ maxNbRecords = null;
+ } else {
+ maxNbRecords = sqlDao.getCount(context);
+ }
}
final Iterator<M> results = paginationIteratorBuilder.build((S) sqlDao, offset, limit, ordering, context);
diff --git a/util/src/main/java/org/killbill/billing/util/entity/DefaultPagination.java b/util/src/main/java/org/killbill/billing/util/entity/DefaultPagination.java
index bdd5e16..7ea60aa 100644
--- a/util/src/main/java/org/killbill/billing/util/entity/DefaultPagination.java
+++ b/util/src/main/java/org/killbill/billing/util/entity/DefaultPagination.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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
* License. You may obtain a copy of the License at:
*
@@ -16,6 +18,8 @@
package org.killbill.billing.util.entity;
+import java.io.Closeable;
+import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
@@ -25,7 +29,7 @@ import javax.annotation.Nullable;
import com.google.common.collect.ImmutableList;
// Assumes the original offset starts at zero.
-public class DefaultPagination<T> implements Pagination<T> {
+public class DefaultPagination<T> implements Pagination<T>, Closeable {
private final Long currentOffset;
private final Long limit;
@@ -75,6 +79,18 @@ public class DefaultPagination<T> implements Pagination<T> {
}
@Override
+ public void close() throws IOException {
+ if (delegateIterator instanceof Closeable) {
+ // Always the case with the current implementation (delegateIterator is a org.skife.jdbi.v2.ResultIterator)
+ ((Closeable) delegateIterator).close();
+ } else {
+ while (delegateIterator.hasNext()) {
+ delegateIterator.next();
+ }
+ }
+ }
+
+ @Override
public Iterator<T> iterator() {
return delegateIterator;
}