killbill-aplcache

catalog: optimize caching Add a test to verify the default

4/3/2015 3:55:02 PM

Details

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 8f31169..ef3806d 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
@@ -36,6 +36,8 @@ import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.annotations.VisibleForTesting;
+
 public class EhCacheCatalogCache implements CatalogCache {
 
     private final Logger logger = LoggerFactory.getLogger(EhCacheCatalogCache.class);
@@ -51,20 +53,19 @@ public class EhCacheCatalogCache implements CatalogCache {
         this.cacheController = cacheControllerDispatcher.getCacheController(CacheType.TENANT_CATALOG);
         this.loader = loader;
         this.cacheLoaderArgument = initializeCacheLoaderArgument(this);
+        setDefaultCatalog();
     }
 
     @Override
     public void loadDefaultCatalog(final String url) throws CatalogApiException {
-        defaultCatalog = (url != null) ? loader.loadDefaultCatalog(url) : null;
+        if (url != null) {
+            defaultCatalog = loader.loadDefaultCatalog(url);
+        }
     }
 
     @Override
     public VersionedCatalog getCatalog(final InternalTenantContext tenantContext) throws CatalogApiException {
         if (tenantContext.getTenantRecordId() == InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID) {
-            if (defaultCatalog == null) {
-                throw new CatalogApiException(ErrorCode.CAT_INVALID_DEFAULT,
-                                              "the system property org.killbill.catalog.uri must be specified and point to valid catalog xml");
-            }
             return defaultCatalog;
         }
         // The cache loader might choke on some bad xml -- unlikely since we check its validity prior storing it,
@@ -108,4 +109,15 @@ public class EhCacheCatalogCache implements CatalogCache {
         final InternalTenantContext notUsed = null;
         return new CacheLoaderArgument(irrelevant, args, notUsed);
     }
+
+    @VisibleForTesting
+    void setDefaultCatalog() {
+        try {
+            // Provided in the classpath
+            this.defaultCatalog = loader.loadDefaultCatalog("EmptyCatalog.xml");
+        } catch (final CatalogApiException e) {
+            this.defaultCatalog = new VersionedCatalog();
+            logger.warn("Exception loading EmptyCatalog - should never happen!", e);
+        }
+    }
 }
diff --git a/catalog/src/main/resources/EmptyCatalog.xml b/catalog/src/main/resources/EmptyCatalog.xml
new file mode 100644
index 0000000..c3d9dff
--- /dev/null
+++ b/catalog/src/main/resources/EmptyCatalog.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!--
+  ~ Copyright 2015 Groupon, Inc
+  ~ Copyright 2015 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
+  ~ License.  You may obtain a copy of the License at:
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+  ~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+  ~ License for the specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:noNamespaceSchemaLocation="CatalogSchema.xsd ">
+
+    <effectiveDate>2013-02-08T00:00:00+00:00</effectiveDate>
+    <catalogName>EmptyCatalog</catalogName>
+
+    <recurringBillingMode>IN_ADVANCE</recurringBillingMode>
+
+    <currencies>
+        <currency>USD</currency>
+    </currencies>
+
+    <products>
+        <product name="empty">
+            <category>BASE</category>
+        </product>
+    </products>
+
+    <rules>
+    </rules>
+
+    <plans>
+        <plan name="empty-monthly">
+            <product>empty</product>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+                    <billingPeriod>MONTHLY</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>USD</currency>
+                            <value>0</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
+    </plans>
+
+    <priceLists>
+        <defaultPriceList name="DEFAULT">
+            <plans>
+                <plan>empty-monthly</plan>
+            </plans>
+        </defaultPriceList>
+    </priceLists>
+</catalog>
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/caching/TestEhCacheCatalogCache.java b/catalog/src/test/java/org/killbill/billing/catalog/caching/TestEhCacheCatalogCache.java
index 998f2ae..d2e4e19 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/caching/TestEhCacheCatalogCache.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/caching/TestEhCacheCatalogCache.java
@@ -30,6 +30,7 @@ import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.CatalogTestSuiteNoDB;
 import org.killbill.billing.catalog.DefaultProduct;
 import org.killbill.billing.catalog.VersionedCatalog;
+import org.killbill.billing.catalog.api.Catalog;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.xmlloader.UriAccessor;
 import org.mockito.Mockito;
@@ -59,15 +60,17 @@ public class TestEhCacheCatalogCache extends CatalogTestSuiteNoDB {
         otherMultiTenantContext = Mockito.mock(InternalCallContext.class);
         Mockito.when(otherMultiTenantContext.getAccountRecordId()).thenReturn(123L);
         Mockito.when(otherMultiTenantContext.getTenantRecordId()).thenReturn(112233L);
+
+        ((EhCacheCatalogCache) catalogCache).setDefaultCatalog();
     }
 
     //
-    // Verify CatalogCache throws CatalogApiException when used in mono-tenant and catalog system property has not been set
+    // Verify CatalogCache returns default Catalog when used in mono-tenant and catalog system property has not been set
     //
-    @Test(groups = "fast", expectedExceptions = CatalogApiException.class)
+    @Test(groups = "fast")
     public void testMissingDefaultCatalog() throws CatalogApiException {
         catalogCache.loadDefaultCatalog(null);
-        catalogCache.getCatalog(internalCallContext);
+        Assert.assertEquals(catalogCache.getCatalog(internalCallContext).getCatalogName(), "EmptyCatalog");
     }
 
     //
@@ -99,9 +102,12 @@ public class TestEhCacheCatalogCache extends CatalogTestSuiteNoDB {
     //
     @Test(groups = "fast")
     public void testExistingTenantCatalog() throws CatalogApiException, URISyntaxException, IOException {
+        final InternalCallContext differentMultiTenantContext = Mockito.mock(InternalCallContext.class);
+        Mockito.when(differentMultiTenantContext.getTenantRecordId()).thenReturn(55667788L);
+
         final AtomicBoolean shouldThrow = new AtomicBoolean(false);
         final Long multiTenantRecordId = multiTenantContext.getTenantRecordId();
-        catalogCache.loadDefaultCatalog(Resources.getResource("SpyCarBasic.xml").toExternalForm());
+        final Long otherMultiTenantRecordId = otherMultiTenantContext.getTenantRecordId();
 
         final InputStream tenantInputCatalog = UriAccessor.accessUri(new URI(Resources.getResource("SpyCarAdvanced.xml").toExternalForm()));
         final String tenantCatalogXML = CharStreams.toString(new InputStreamReader(tenantInputCatalog, "UTF-8"));
@@ -116,12 +122,31 @@ public class TestEhCacheCatalogCache extends CatalogTestSuiteNoDB {
                 final InternalTenantContext internalContext = (InternalTenantContext) invocation.getArguments()[0];
                 if (multiTenantRecordId.equals(internalContext.getTenantRecordId())) {
                     return ImmutableList.<String>of(tenantCatalogXML);
-                } else {
+                } else if (otherMultiTenantRecordId.equals(internalContext.getTenantRecordId())) {
                     return ImmutableList.<String>of(otherTenantCatalogXML);
+                } else {
+                    return ImmutableList.<String>of();
                 }
             }
         });
 
+        // Verify the lookup for a non-cached tenant. No system config is set yet but EhCacheCatalogCache returns a default empty one
+        VersionedCatalog differentResult = catalogCache.getCatalog(differentMultiTenantContext);
+        Assert.assertNotNull(differentResult);
+        Assert.assertEquals(differentResult.getCatalogName(), "EmptyCatalog");
+
+        // Make sure the cache loader isn't invoked, see https://github.com/killbill/killbill/issues/300
+        shouldThrow.set(true);
+
+        differentResult = catalogCache.getCatalog(differentMultiTenantContext);
+        Assert.assertNotNull(differentResult);
+        Assert.assertEquals(differentResult.getCatalogName(), "EmptyCatalog");
+
+        shouldThrow.set(false);
+
+        // Set a default config
+        catalogCache.loadDefaultCatalog(Resources.getResource("SpyCarBasic.xml").toExternalForm());
+
         // Verify the lookup for this tenant
         final VersionedCatalog result = catalogCache.getCatalog(multiTenantContext);
         Assert.assertNotNull(result);