diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithCatalogPlugin.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithCatalogPlugin.java
index b2f1779..a67a191 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithCatalogPlugin.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithCatalogPlugin.java
@@ -35,6 +35,8 @@ import org.killbill.billing.catalog.StandaloneCatalog;
import org.killbill.billing.catalog.StandaloneCatalogWithPriceOverride;
import org.killbill.billing.catalog.VersionedCatalog;
import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.Catalog;
+import org.killbill.billing.catalog.api.CatalogUserApi;
import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.PriceList;
import org.killbill.billing.catalog.api.Product;
@@ -53,7 +55,9 @@ import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.callcontext.TenantContext;
import org.killbill.xmlloader.XMLLoader;
+import org.testng.Assert;
import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.google.common.base.Function;
@@ -72,6 +76,9 @@ public class TestWithCatalogPlugin extends TestIntegrationBase {
@Inject
private InternalCallContextFactory internalCallContextFactory;
+ @Inject
+ private CatalogUserApi catalogUserApi;
+
private TestCatalogPluginApi testCatalogPluginApi;
@BeforeClass(groups = "slow")
@@ -97,8 +104,17 @@ public class TestWithCatalogPlugin extends TestIntegrationBase {
}, testCatalogPluginApi);
}
+ @BeforeMethod(groups = "slow")
+ public void beforeMethod() throws Exception {
+ super.beforeMethod();
+ testCatalogPluginApi.reset();
+ }
+
@Test(groups = "slow")
public void testCreateSubscriptionWithCatalogPlugin() throws Exception {
+
+ testCatalogPluginApi.addCatalogVersion("WeaponsHire.xml");
+
// We take april as it has 30 days (easier to play with BCD)
// Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
clock.setDay(new LocalDate(2012, 4, 1));
@@ -111,26 +127,120 @@ public class TestWithCatalogPlugin extends TestIntegrationBase {
final DefaultEntitlement bpSubscription = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
subscriptionChecker.checkSubscriptionCreated(bpSubscription.getId(), internalCallContext);
+
+ // Code went to retrieve catalog more than one time
+ Assert.assertTrue(testCatalogPluginApi.getNbLatestCatalogVersionApiCalls() > 1);
+
+ // Code only retrieved catalog from plugin once (caching works!)
+ Assert.assertEquals(testCatalogPluginApi.getNbVersionedPluginCatalogApiCalls(), 1);
+ }
+
+
+ @Test(groups = "slow")
+ public void testWithMultipleVersions() throws Exception {
+
+ testCatalogPluginApi.addCatalogVersion("versionedCatalog/WeaponsHireSmall-1.xml");
+
+ final VersionedCatalog catalog1 = (VersionedCatalog) catalogUserApi.getCatalog("whatever", callContext);
+ Assert.assertEquals(testCatalogPluginApi.getNbLatestCatalogVersionApiCalls(), 1);
+ Assert.assertEquals(testCatalogPluginApi.getNbVersionedPluginCatalogApiCalls(), 1);
+ Assert.assertEquals(catalog1.getEffectiveDate().compareTo(testCatalogPluginApi.getLatestCatalogUpdate().toDate()), 0);
+
+ // Retrieve 3 more times
+ catalogUserApi.getCatalog("whatever", callContext);
+ catalogUserApi.getCatalog("whatever", callContext);
+ catalogUserApi.getCatalog("whatever", callContext);
+ Assert.assertEquals(testCatalogPluginApi.getNbLatestCatalogVersionApiCalls(), 4);
+ Assert.assertEquals(testCatalogPluginApi.getNbVersionedPluginCatalogApiCalls(), 1);
+
+ testCatalogPluginApi.addCatalogVersion("versionedCatalog/WeaponsHireSmall-2.xml");
+
+ final VersionedCatalog catalog2 = (VersionedCatalog) catalogUserApi.getCatalog("whatever", callContext);
+ Assert.assertEquals(testCatalogPluginApi.getNbLatestCatalogVersionApiCalls(), 5);
+ Assert.assertEquals(testCatalogPluginApi.getNbVersionedPluginCatalogApiCalls(), 2);
+ Assert.assertEquals(catalog2.getEffectiveDate().compareTo(testCatalogPluginApi.getLatestCatalogUpdate().toDate()), 0);
+
+ testCatalogPluginApi.addCatalogVersion("versionedCatalog/WeaponsHireSmall-3.xml");
+
+ final VersionedCatalog catalog3 = (VersionedCatalog) catalogUserApi.getCatalog("whatever", callContext);
+ Assert.assertEquals(testCatalogPluginApi.getNbLatestCatalogVersionApiCalls(), 6);
+ Assert.assertEquals(testCatalogPluginApi.getNbVersionedPluginCatalogApiCalls(), 3);
+ Assert.assertEquals(catalog3.getEffectiveDate().compareTo(testCatalogPluginApi.getLatestCatalogUpdate().toDate()), 0);
+
+
+ // Retrieve 4 more times
+ catalogUserApi.getCatalog("whatever", callContext);
+ catalogUserApi.getCatalog("whatever", callContext);
+ catalogUserApi.getCatalog("whatever", callContext);
+ catalogUserApi.getCatalog("whatever", callContext);
+ Assert.assertEquals(testCatalogPluginApi.getNbLatestCatalogVersionApiCalls(), 10);
+ Assert.assertEquals(testCatalogPluginApi.getNbVersionedPluginCatalogApiCalls(), 3);
+
}
+
public static class TestCatalogPluginApi implements CatalogPluginApi {
- private final VersionedCatalog versionedCatalog;
+ final PriceOverride priceOverride;
+ final InternalTenantContext internalTenantContext;
+ final InternalCallContextFactory internalCallContextFactory;
+
+ private VersionedCatalog versionedCatalog;
+ private DateTime latestCatalogUpdate;
+ private int nbLatestCatalogVersionApiCalls;
+ private int nbVersionedPluginCatalogApiCalls;
public TestCatalogPluginApi(final PriceOverride priceOverride, final InternalTenantContext internalTenantContext, final InternalCallContextFactory internalCallContextFactory) throws Exception {
- final StandaloneCatalog inputCatalog = XMLLoader.getObjectFromString(Resources.getResource("WeaponsHire.xml").toExternalForm(), StandaloneCatalog.class);
- final List<StandaloneCatalog> versions = new ArrayList<StandaloneCatalog>();
- final StandaloneCatalogWithPriceOverride standaloneCatalogWithPriceOverride = new StandaloneCatalogWithPriceOverride(inputCatalog, priceOverride, internalTenantContext.getTenantRecordId(), internalCallContextFactory);
- versions.add(standaloneCatalogWithPriceOverride);
- versionedCatalog = new VersionedCatalog(getClock());
- versionedCatalog.addAll(versions);
+ this.priceOverride = priceOverride;
+ this.internalTenantContext = internalTenantContext;
+ this.internalCallContextFactory = internalCallContextFactory;
+ reset();
+ }
+
+ @Override
+ public DateTime getLatestCatalogVersion(final Iterable<PluginProperty> iterable, final TenantContext tenantContext) {
+ nbLatestCatalogVersionApiCalls++;
+ return latestCatalogUpdate;
}
@Override
public VersionedPluginCatalog getVersionedPluginCatalog(final Iterable<PluginProperty> properties, final TenantContext tenantContext) {
+ nbVersionedPluginCatalogApiCalls++;
+ Assert.assertNotNull(versionedCatalog, "test did not initialize plugin catalog");
return new TestModelVersionedPluginCatalog(versionedCatalog.getCatalogName(), versionedCatalog.getRecurringBillingMode(), toStandalonePluginCatalogs(versionedCatalog.getVersions()));
}
+ public void addCatalogVersion(final String catalogResource) throws Exception {
+
+ final StandaloneCatalog inputCatalogVersion = XMLLoader.getObjectFromString(Resources.getResource(catalogResource).toExternalForm(), StandaloneCatalog.class);
+ final StandaloneCatalogWithPriceOverride inputCatalogVersionWithOverride = new StandaloneCatalogWithPriceOverride(inputCatalogVersion, priceOverride, internalTenantContext.getTenantRecordId(), internalCallContextFactory);
+
+ this.latestCatalogUpdate = new DateTime(inputCatalogVersion.getEffectiveDate());
+ if (versionedCatalog == null) {
+ versionedCatalog = new VersionedCatalog(getClock());
+ }
+ versionedCatalog.add(inputCatalogVersionWithOverride);
+ }
+
+ public void reset() {
+ this.versionedCatalog = null;
+ this.latestCatalogUpdate = null;
+ this.nbLatestCatalogVersionApiCalls = 0;
+ this.nbVersionedPluginCatalogApiCalls = 0;
+ }
+
+ public int getNbLatestCatalogVersionApiCalls() {
+ return nbLatestCatalogVersionApiCalls;
+ }
+
+ public int getNbVersionedPluginCatalogApiCalls() {
+ return nbVersionedPluginCatalogApiCalls;
+ }
+
+ public DateTime getLatestCatalogUpdate() {
+ return latestCatalogUpdate;
+ }
+
private Iterable<StandalonePluginCatalog> toStandalonePluginCatalogs(final List<StandaloneCatalog> input) {
return Iterables.transform(input, new Function<StandaloneCatalog, StandalonePluginCatalog>() {
@Override
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheCatalogCache.java b/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheCatalogCache.java
index ee21422..6a9c625 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheCatalogCache.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheCatalogCache.java
@@ -21,6 +21,7 @@ import java.util.List;
import javax.inject.Inject;
+import org.joda.time.DateTime;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.ObjectType;
import org.killbill.billing.callcontext.InternalTenantContext;
@@ -91,7 +92,6 @@ public class EhCacheCatalogCache implements CatalogCache {
@Override
public VersionedCatalog getCatalog(final boolean useDefaultCatalog, final boolean filterTemplateCatalog, final InternalTenantContext tenantContext) throws CatalogApiException {
- // STEPH TODO what are the possibilities for caching here ?
final VersionedCatalog pluginVersionedCatalog = getCatalogFromPlugins(tenantContext);
if (pluginVersionedCatalog != null) {
return pluginVersionedCatalog;
@@ -132,11 +132,35 @@ public class EhCacheCatalogCache implements CatalogCache {
final TenantContext tenantContext = internalCallContextFactory.createTenantContext(internalTenantContext);
for (final String service : pluginRegistry.getAllServices()) {
final CatalogPluginApi plugin = pluginRegistry.getServiceForName(service);
+
+ //
+ // Beware plugin implementors: latestCatalogUpdatedDate returned by the plugin should also match the effectiveDate of the VersionedCatalog.
+ //
+ // However, this is the plugin choice to return one, or many catalog versions (StandaloneCatalog), Kill Bill catalog module does not care.
+ // As a guideline, if plugin keeps seeing new Plans/Products, this can all fit into the same version; however if there is a true versioning
+ // (e.g deleted Plans...), then multiple versions must be returned.
+ //
+ final DateTime latestCatalogUpdatedDate = plugin.getLatestCatalogVersion(ImmutableList.<PluginProperty>of(), tenantContext);
+ // A null latestCatalogUpdatedDate by passing caching, by fetching full catalog from plugin below (compatibility mode with 0.18.x or non optimized plugin api mode)
+ //
+ if (latestCatalogUpdatedDate != null) {
+ final VersionedCatalog tenantCatalog = (VersionedCatalog) cacheController.get(internalTenantContext.getTenantRecordId());
+ if (tenantCatalog != null) {
+ if (tenantCatalog.getEffectiveDate().compareTo(latestCatalogUpdatedDate.toDate()) == 0) {
+ // Current cached version matches the one from the plugin
+ return tenantCatalog;
+ }
+ }
+ }
+
final VersionedPluginCatalog pluginCatalog = plugin.getVersionedPluginCatalog(ImmutableList.<PluginProperty>of(), tenantContext);
// First plugin that gets something (for that tenant) returns it
if (pluginCatalog != null) {
logger.info("Returning catalog from plugin {} on tenant {} ", service, internalTenantContext.getTenantRecordId());
- return versionedCatalogMapper.toVersionedCatalog(pluginCatalog, internalTenantContext);
+ final VersionedCatalog resolvedPluginCatalog = versionedCatalogMapper.toVersionedCatalog(pluginCatalog, internalTenantContext);
+ cacheController.remove(internalTenantContext.getTenantRecordId());
+ cacheController.add(internalTenantContext.getTenantRecordId(), resolvedPluginCatalog);
+ return resolvedPluginCatalog;
}
}
return null;