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 acfac0c..970e3f7 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
@@ -1,6 +1,6 @@
/*
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-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
@@ -63,23 +63,23 @@ public class EhCacheCatalogCache implements CatalogCache {
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");
+ "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,
// but to be on the safe side;;
try {
- final VersionedCatalog tenantCatalog = (VersionedCatalog) cacheController.get(tenantContext, cacheLoaderArgument);
+ final VersionedCatalog tenantCatalog = (VersionedCatalog) cacheController.get(tenantContext.getTenantRecordId(), cacheLoaderArgument);
return (tenantCatalog != null) ? tenantCatalog : defaultCatalog;
- } catch (IllegalStateException e) {
+ } catch (final IllegalStateException e) {
throw new CatalogApiException(ErrorCode.CAT_INVALID_FOR_TENANT, tenantContext.getTenantRecordId());
}
}
@Override
public void clearCatalog(final InternalTenantContext tenantContext) {
- cacheController.remove(tenantContext);
+ cacheController.remove(tenantContext.getTenantRecordId());
}
//
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 336f691..611378c 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
@@ -1,6 +1,6 @@
/*
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-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
@@ -22,15 +22,19 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.killbill.billing.callcontext.InternalCallContext;
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.CatalogApiException;
-import org.killbill.billing.tenant.api.TenantInternalApi.CacheInvalidationCallback;
import org.killbill.xmlloader.UriAccessor;
import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
@@ -41,11 +45,20 @@ import com.google.common.io.Resources;
public class TestEhCacheCatalogCache extends CatalogTestSuiteNoDB {
- final CacheInvalidationCallback cacheInvalidationCallback = Mockito.mock(CacheInvalidationCallback.class);
+ private InternalTenantContext multiTenantContext;
+ private InternalTenantContext otherMultiTenantContext;
@BeforeMethod(groups = "fast")
protected void beforeMethod() throws Exception {
cacheControllerDispatcher.clearAll();
+
+ multiTenantContext = Mockito.mock(InternalTenantContext.class);
+ Mockito.when(multiTenantContext.getAccountRecordId()).thenReturn(456L);
+ Mockito.when(multiTenantContext.getTenantRecordId()).thenReturn(99L);
+
+ otherMultiTenantContext = Mockito.mock(InternalCallContext.class);
+ Mockito.when(otherMultiTenantContext.getAccountRecordId()).thenReturn(123L);
+ Mockito.when(otherMultiTenantContext.getTenantRecordId()).thenReturn(112233L);
}
//
@@ -53,11 +66,7 @@ public class TestEhCacheCatalogCache extends CatalogTestSuiteNoDB {
//
@Test(groups = "fast", expectedExceptions = CatalogApiException.class)
public void testMissingDefaultCatalog() throws CatalogApiException {
-
- final InternalTenantContext tenantContext = Mockito.mock(InternalTenantContext.class);
- Mockito.when(tenantContext.getTenantRecordId()).thenReturn(0L);
catalogCache.loadDefaultCatalog(null);
- Mockito.when(tenantInternalApi.getTenantCatalogs(tenantContext)).thenReturn(ImmutableList.<String>of());
catalogCache.getCatalog(internalCallContext);
}
@@ -66,34 +75,18 @@ public class TestEhCacheCatalogCache extends CatalogTestSuiteNoDB {
//
@Test(groups = "fast")
public void testDefaultCatalog() throws CatalogApiException {
-
- final InternalTenantContext tenantContext = Mockito.mock(InternalTenantContext.class);
- Mockito.when(tenantContext.getTenantRecordId()).thenReturn(0L);
-
catalogCache.loadDefaultCatalog(Resources.getResource("SpyCarBasic.xml").toExternalForm());
- Mockito.when(tenantInternalApi.getTenantCatalogs(tenantContext)).thenReturn(ImmutableList.<String>of());
- VersionedCatalog result = catalogCache.getCatalog(internalCallContext);
- Assert.assertNotNull(result);
- final DefaultProduct[] products = result.getProducts(clock.getUTCNow());
- Assert.assertEquals(products.length, 3);
- }
- //
- // Verify CatalogCache returns default catalog for the (non 0) tenant when its tenant catalog has not been uploaded
- //
- @Test(groups = "fast")
- public void testMissingTenantCatalog() throws CatalogApiException, URISyntaxException, IOException {
-
- catalogCache.loadDefaultCatalog(Resources.getResource("SpyCarBasic.xml").toExternalForm());
-
- final InternalTenantContext tenantContext = Mockito.mock(InternalTenantContext.class);
- Mockito.when(tenantContext.getTenantRecordId()).thenReturn(99L);
-
- Mockito.when(tenantInternalApi.getTenantCatalogs(Mockito.any(InternalTenantContext.class))).thenReturn(ImmutableList.<String>of());
- VersionedCatalog result = catalogCache.getCatalog(tenantContext);
+ final VersionedCatalog result = catalogCache.getCatalog(internalCallContext);
Assert.assertNotNull(result);
final DefaultProduct[] products = result.getProducts(clock.getUTCNow());
Assert.assertEquals(products.length, 3);
+
+ // Verify the lookup with other contexts
+ Assert.assertEquals(catalogCache.getCatalog(multiTenantContext), result);
+ Assert.assertEquals(catalogCache.getCatalog(otherMultiTenantContext), result);
+ Assert.assertEquals(catalogCache.getCatalog(Mockito.mock(InternalTenantContext.class)), result);
+ Assert.assertEquals(catalogCache.getCatalog(Mockito.mock(InternalCallContext.class)), result);
}
//
@@ -104,26 +97,54 @@ public class TestEhCacheCatalogCache extends CatalogTestSuiteNoDB {
//
@Test(groups = "fast")
public void testExistingTenantCatalog() throws CatalogApiException, URISyntaxException, IOException {
-
+ final AtomicBoolean shouldThrow = new AtomicBoolean(false);
+ final Long multiTenantRecordId = multiTenantContext.getTenantRecordId();
catalogCache.loadDefaultCatalog(Resources.getResource("SpyCarBasic.xml").toExternalForm());
- final InputStream inputCatalog = UriAccessor.accessUri(new URI(Resources.getResource("SpyCarAdvanced.xml").toExternalForm()));
- final String catalogXML = CharStreams.toString(new InputStreamReader(inputCatalog, "UTF-8"));
-
- final InternalTenantContext tenantContext = Mockito.mock(InternalTenantContext.class);
- Mockito.when(tenantContext.getTenantRecordId()).thenReturn(156L);
-
- Mockito.when(tenantInternalApi.getTenantCatalogs(Mockito.any(InternalTenantContext.class))).thenReturn(ImmutableList.<String>of(catalogXML));
- VersionedCatalog result = catalogCache.getCatalog(tenantContext);
+ final InputStream tenantInputCatalog = UriAccessor.accessUri(new URI(Resources.getResource("SpyCarAdvanced.xml").toExternalForm()));
+ final String tenantCatalogXML = CharStreams.toString(new InputStreamReader(tenantInputCatalog, "UTF-8"));
+ final InputStream otherTenantInputCatalog = UriAccessor.accessUri(new URI(Resources.getResource("SpyCarBasic.xml").toExternalForm()));
+ final String otherTenantCatalogXML = CharStreams.toString(new InputStreamReader(otherTenantInputCatalog, "UTF-8"));
+ Mockito.when(tenantInternalApi.getTenantCatalogs(Mockito.any(InternalTenantContext.class))).thenAnswer(new Answer<List<String>>() {
+ @Override
+ public List<String> answer(final InvocationOnMock invocation) throws Throwable {
+ if (shouldThrow.get()) {
+ throw new RuntimeException();
+ }
+ final InternalTenantContext internalContext = (InternalTenantContext) invocation.getArguments()[0];
+ if (multiTenantRecordId.equals(internalContext.getTenantRecordId())) {
+ return ImmutableList.<String>of(tenantCatalogXML);
+ } else {
+ return ImmutableList.<String>of(otherTenantCatalogXML);
+ }
+ }
+ });
+
+ // Verify the lookup for this tenant
+ final VersionedCatalog result = catalogCache.getCatalog(multiTenantContext);
Assert.assertNotNull(result);
final DefaultProduct[] products = result.getProducts(clock.getUTCNow());
Assert.assertEquals(products.length, 6);
- Mockito.when(tenantInternalApi.getTenantCatalogs(tenantContext)).thenThrow(RuntimeException.class);
+ // Verify the lookup for another tenant
+ final VersionedCatalog otherResult = catalogCache.getCatalog(otherMultiTenantContext);
+ Assert.assertNotNull(otherResult);
+ final DefaultProduct[] otherProducts = otherResult.getProducts(clock.getUTCNow());
+ Assert.assertEquals(otherProducts.length, 3);
+
+ shouldThrow.set(true);
+
+ // Verify the lookup for this tenant
+ final VersionedCatalog result2 = catalogCache.getCatalog(multiTenantContext);
+ Assert.assertEquals(result2, result);
+
+ // Verify the lookup with another context for the same tenant
+ final InternalCallContext sameMultiTenantContext = Mockito.mock(InternalCallContext.class);
+ Mockito.when(sameMultiTenantContext.getAccountRecordId()).thenReturn(9102L);
+ Mockito.when(sameMultiTenantContext.getTenantRecordId()).thenReturn(multiTenantRecordId);
+ Assert.assertEquals(catalogCache.getCatalog(sameMultiTenantContext), result);
- VersionedCatalog result2 = catalogCache.getCatalog(tenantContext);
- Assert.assertNotNull(result2);
- final DefaultProduct[] products2 = result.getProducts(clock.getUTCNow());
- Assert.assertEquals(products2.length, 6);
+ // Verify the lookup with the other tenant
+ Assert.assertEquals(catalogCache.getCatalog(otherMultiTenantContext), otherResult);
}
}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/TenantCatalogCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/TenantCatalogCacheLoader.java
index 8630e4d..8da4461 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/TenantCatalogCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/TenantCatalogCacheLoader.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-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
@@ -25,7 +25,6 @@ import javax.inject.Singleton;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.tenant.api.TenantInternalApi;
-import org.killbill.billing.tenant.api.TenantInternalApi.CacheInvalidationCallback;
import org.killbill.billing.util.cache.Cachable.CacheType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -52,14 +51,15 @@ public class TenantCatalogCacheLoader extends BaseCacheLoader {
public Object load(final Object key, final Object argument) {
checkCacheLoaderStatus();
- if (!(key instanceof InternalTenantContext)) {
+ if (!(key instanceof Long)) {
throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
}
if (!(argument instanceof CacheLoaderArgument)) {
throw new IllegalArgumentException("Unexpected argument type of " + argument.getClass().getName());
}
- final InternalTenantContext internalTenantContext = (InternalTenantContext) key;
+ final Long tenantRecordId = (Long) key;
+ final InternalTenantContext internalTenantContext = new InternalTenantContext(tenantRecordId);
final CacheLoaderArgument cacheLoaderArgument = (CacheLoaderArgument) argument;
if (cacheLoaderArgument.getArgs() == null || !(cacheLoaderArgument.getArgs()[0] instanceof LoaderCallback)) {
@@ -74,13 +74,14 @@ public class TenantCatalogCacheLoader extends BaseCacheLoader {
try {
logger.info("Loading catalog cache for tenant " + internalTenantContext.getTenantRecordId());
return callback.loadCatalog(catalogXMLs);
- } catch (CatalogApiException e) {
+ } catch (final CatalogApiException e) {
throw new IllegalStateException(String.format("Failed to de-serialize catalog for tenant %s : %s",
internalTenantContext.getTenantRecordId(), e.getMessage()), e);
}
}
public interface LoaderCallback {
+
public Object loadCatalog(final List<String> catalogXMLs) throws CatalogApiException;
}
}