killbill-memoizeit

subscription: add new immutable caches Map bundleId -> accountId

4/19/2018 7:05:51 AM

Changes

Details

diff --git a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
index 2a05cf6..ac1df1a 100644
--- a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
+++ b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
@@ -76,8 +76,6 @@ public interface SubscriptionBaseInternalApi {
 
     public SubscriptionBaseBundle getBundleFromId(UUID id, InternalTenantContext context) throws SubscriptionBaseApiException;
 
-    public UUID getAccountIdFromSubscriptionId(UUID subscriptionId, InternalTenantContext context) throws SubscriptionBaseApiException;
-
     public void setChargedThroughDate(UUID subscriptionId, DateTime chargedThruDate, InternalCallContext context) throws SubscriptionBaseApiException;
 
     public List<EffectiveSubscriptionInternalEvent> getAllTransitions(SubscriptionBase subscription, InternalTenantContext context);
@@ -94,4 +92,10 @@ public interface SubscriptionBaseInternalApi {
     public void updateBCD(final UUID subscriptionId, final int bcd, @Nullable final LocalDate effectiveFromDate, final InternalCallContext internalCallContext) throws SubscriptionBaseApiException;
 
     public int getDefaultBillCycleDayLocal(final Map<UUID, Integer> bcdCache, final SubscriptionBase subscription, final SubscriptionBase baseSubscription, final PlanPhaseSpecifier planPhaseSpecifier, final int accountBillCycleDayLocal, final Catalog catalog, final InternalTenantContext context) throws SubscriptionBaseApiException;
+
+    public UUID getAccountIdFromBundleId(UUID bundleId, InternalTenantContext context) throws SubscriptionBaseApiException;
+
+    public UUID getBundleIdFromSubscriptionId(UUID entitlementId, InternalTenantContext context) throws SubscriptionBaseApiException;
+
+    public UUID getAccountIdFromSubscriptionId(UUID subscriptionId, InternalTenantContext context) throws SubscriptionBaseApiException;
 }
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java
index f05669d..a5e8793 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java
@@ -333,10 +333,9 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements 
     public List<EntitlementAOStatusDryRun> getDryRunStatusForChange(final UUID bundleId, final String targetProductName, @Nullable final LocalDate effectiveDate, final TenantContext context) throws EntitlementApiException {
         final InternalTenantContext internalContext = internalCallContextFactory.createInternalTenantContext(bundleId, ObjectType.BUNDLE, context);
         try {
-            final SubscriptionBaseBundle bundle = subscriptionBaseInternalApi.getBundleFromId(bundleId, internalContext);
             final SubscriptionBase baseSubscription = subscriptionBaseInternalApi.getBaseSubscription(bundleId, internalContext);
-
-            final InternalTenantContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalTenantContext(bundle.getAccountId(), context);
+            final UUID accountId = subscriptionBaseInternalApi.getAccountIdFromBundleId(bundleId, internalContext);
+            final InternalTenantContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalTenantContext(accountId, context);
             final DateTime now = clock.getUTCNow();
             final DateTime requestedDate = dateHelper.fromLocalDateAndReferenceTime(effectiveDate, now, contextWithValidAccountRecordId);
             return subscriptionBaseInternalApi.getDryRunChangePlanStatus(baseSubscription.getId(), targetProductName, requestedDate, contextWithValidAccountRecordId);
@@ -356,7 +355,7 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements 
         final InternalTenantContext internalContext = internalCallContextFactory.createInternalTenantContext(bundleId, ObjectType.BUNDLE, tenantContext);
         final UUID accountId;
         try {
-            accountId = subscriptionBaseInternalApi.getBundleFromId(bundleId, internalContext).getAccountId();
+            accountId = subscriptionBaseInternalApi.getAccountIdFromBundleId(bundleId, internalContext);
         } catch (final SubscriptionBaseApiException e) {
             throw new EntitlementApiException(e);
         }
@@ -465,12 +464,12 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements 
                 try {
 
                     final UUID activeSubscriptionIdForKey = entitlementUtils.getFirstActiveSubscriptionIdForKeyOrNull(externalKey, contextWithSourceAccountRecordId);
-                    final SubscriptionBase baseSubscription = activeSubscriptionIdForKey != null ?
-                                                              subscriptionBaseInternalApi.getSubscriptionFromId(activeSubscriptionIdForKey, contextWithSourceAccountRecordId) : null;
-                    final SubscriptionBaseBundle baseBundle = baseSubscription != null ?
-                                                              subscriptionBaseInternalApi.getBundleFromId(baseSubscription.getBundleId(), contextWithSourceAccountRecordId) : null;
+                    final UUID bundleId = activeSubscriptionIdForKey != null ?
+                                          subscriptionBaseInternalApi.getBundleIdFromSubscriptionId(activeSubscriptionIdForKey, contextWithSourceAccountRecordId) : null;
+                    final UUID baseBundleAccountId = bundleId != null ?
+                                                     subscriptionBaseInternalApi.getAccountIdFromBundleId(bundleId, contextWithSourceAccountRecordId) : null;
 
-                    if (baseBundle == null || !baseBundle.getAccountId().equals(sourceAccountId)) {
+                    if (baseBundleAccountId == null || !baseBundleAccountId.equals(sourceAccountId)) {
                         throw new EntitlementApiException(new SubscriptionBaseApiException(ErrorCode.SUB_GET_INVALID_BUNDLE_KEY, externalKey));
                     }
 
@@ -485,7 +484,7 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements 
 
                     // Block all associated subscriptions - TODO Do we want to block the bundle as well (this will add an extra STOP_ENTITLEMENT event in the bundle timeline stream)?
                     // Note that there is no un-transfer at the moment, so we effectively add a blocking state on disk for all subscriptions
-                    for (final SubscriptionBase subscriptionBase : subscriptionBaseInternalApi.getSubscriptionsForBundle(baseBundle.getId(), null, contextWithSourceAccountRecordId)) {
+                    for (final SubscriptionBase subscriptionBase : subscriptionBaseInternalApi.getSubscriptionsForBundle(bundleId, null, contextWithSourceAccountRecordId)) {
                         final BlockingState blockingState = new DefaultBlockingState(subscriptionBase.getId(), BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_CANCELLED, EntitlementService.ENTITLEMENT_SERVICE_NAME, true, true, false, requestedDate);
                         blockingStates.put(blockingState, subscriptionBase.getBundleId());
                     }
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionApi.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionApi.java
index 7316ff1..a9691ee 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionApi.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionApi.java
@@ -195,8 +195,8 @@ public class DefaultSubscriptionApi implements SubscriptionApi {
             }
 
             final InternalTenantContext internalContextWithAccountRecordId =  internalCallContextFactory.createInternalTenantContext(activeSubscriptionIdForKey, ObjectType.SUBSCRIPTION, context);
-            final SubscriptionBase subscriptionBase = subscriptionBaseInternalApi.getSubscriptionFromId(activeSubscriptionIdForKey, internalContextWithAccountRecordId);
-            return getSubscriptionBundle(subscriptionBase.getBundleId(), context);
+            final UUID bundleId = subscriptionBaseInternalApi.getBundleIdFromSubscriptionId(activeSubscriptionIdForKey, internalContextWithAccountRecordId);
+            return getSubscriptionBundle(bundleId, context);
         } catch (final SubscriptionBaseApiException e) {
             throw new SubscriptionApiException(e);
         }
@@ -276,11 +276,10 @@ public class DefaultSubscriptionApi implements SubscriptionApi {
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(bundleId, ObjectType.BUNDLE, callContext);
 
-        final SubscriptionBaseBundle bundle;
         final ImmutableAccountData account;
         try {
-            bundle = subscriptionBaseInternalApi.getBundleFromId(bundleId, internalCallContext);
-            account = accountApi.getImmutableAccountDataById(bundle.getAccountId(), internalCallContext);
+            final UUID accountId = subscriptionBaseInternalApi.getAccountIdFromBundleId(bundleId, internalCallContext);
+            account = accountApi.getImmutableAccountDataById(accountId, internalCallContext);
         } catch (final SubscriptionBaseApiException e) {
             throw new EntitlementApiException(e);
         } catch (AccountApiException e) {
@@ -299,7 +298,7 @@ public class DefaultSubscriptionApi implements SubscriptionApi {
         final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
         baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementWithAddOnsSpecifier);
         final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.UPDATE_BUNDLE_EXTERNAL_KEY,
-                                                                               bundle.getAccountId(),
+                                                                               account.getId(),
                                                                                null,
                                                                                baseEntitlementWithAddOnsSpecifierList,
                                                                                null,
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/block/DefaultBlockingChecker.java b/entitlement/src/main/java/org/killbill/billing/entitlement/block/DefaultBlockingChecker.java
index d793106..20f76a8 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/block/DefaultBlockingChecker.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/block/DefaultBlockingChecker.java
@@ -136,44 +136,41 @@ public class DefaultBlockingChecker implements BlockingChecker {
     }
 
     private DefaultBlockingAggregator getBlockedStateSubscriptionId(final UUID subscriptionId, final DateTime upToDate, final InternalTenantContext context) throws BlockingApiException {
-        final SubscriptionBase subscription;
         try {
-            subscription = subscriptionApi.getSubscriptionFromId(subscriptionId, context);
-            return getBlockedStateSubscription(subscription, upToDate, context);
+            final UUID bundleId = subscriptionApi.getBundleIdFromSubscriptionId(subscriptionId, context);
+            return getBlockedStateSubscription(bundleId, subscriptionId, upToDate, context);
         } catch (final SubscriptionBaseApiException e) {
             throw new BlockingApiException(e, ErrorCode.fromCode(e.getCode()));
         }
     }
 
-    private DefaultBlockingAggregator getBlockedStateSubscription(final SubscriptionBase subscription, final DateTime upToDate, final InternalTenantContext context) throws BlockingApiException {
+    private DefaultBlockingAggregator getBlockedStateSubscription(@Nullable final UUID bundleId, @Nullable final UUID subscriptionId, final DateTime upToDate, final InternalTenantContext context) throws BlockingApiException {
         final DefaultBlockingAggregator result = new DefaultBlockingAggregator();
-        if (subscription != null) {
-            final DefaultBlockingAggregator subscriptionState = getBlockedStateForId(subscription.getId(), BlockingStateType.SUBSCRIPTION, upToDate, context);
+        if (subscriptionId != null) {
+            final DefaultBlockingAggregator subscriptionState = getBlockedStateForId(subscriptionId, BlockingStateType.SUBSCRIPTION, upToDate, context);
             if (subscriptionState != null) {
                 result.or(subscriptionState);
             }
-            if (subscription.getBundleId() != null) {
+            if (bundleId != null) {
                 // Recursive call to also fetch account state
-                result.or(getBlockedStateBundleId(subscription.getBundleId(), upToDate, context));
+                result.or(getBlockedStateBundleId(bundleId, upToDate, context));
             }
         }
         return result;
     }
 
     private DefaultBlockingAggregator getBlockedStateBundleId(final UUID bundleId, final DateTime upToDate, final InternalTenantContext context) throws BlockingApiException {
-
-        final SubscriptionBaseBundle bundle;
         try {
-            bundle = subscriptionApi.getBundleFromId(bundleId, context);
-            return getBlockedStateBundle(bundle, upToDate, context);
+            final UUID accountId = subscriptionApi.getAccountIdFromBundleId(bundleId, context);
+            return getBlockedStateBundle(accountId, bundleId, upToDate, context);
         } catch (final SubscriptionBaseApiException e) {
             throw new BlockingApiException(e, ErrorCode.fromCode(e.getCode()));
         }
     }
 
-    private DefaultBlockingAggregator getBlockedStateBundle(final SubscriptionBaseBundle bundle, final DateTime upToDate, final InternalTenantContext context) {
-        final DefaultBlockingAggregator result = getBlockedStateAccountId(bundle.getAccountId(), upToDate, context);
-        final DefaultBlockingAggregator bundleState = getBlockedStateForId(bundle.getId(), BlockingStateType.SUBSCRIPTION_BUNDLE, upToDate, context);
+    private DefaultBlockingAggregator getBlockedStateBundle(final UUID accountId, final UUID bundleId, final DateTime upToDate, final InternalTenantContext context) {
+        final DefaultBlockingAggregator result = getBlockedStateAccountId(accountId, upToDate, context);
+        final DefaultBlockingAggregator bundleState = getBlockedStateForId(bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE, upToDate, context);
         if (bundleState != null) {
             result.or(bundleState);
         }
@@ -220,9 +217,9 @@ public class DefaultBlockingChecker implements BlockingChecker {
 
     @Override
     public void checkBlockedChange(final Blockable blockable, final DateTime upToDate, final InternalTenantContext context) throws BlockingApiException {
-        if (blockable instanceof SubscriptionBase && getBlockedStateSubscription((SubscriptionBase) blockable, upToDate, context).isBlockChange()) {
+        if (blockable instanceof SubscriptionBase && getBlockedStateSubscription(((SubscriptionBase) blockable).getBundleId(), blockable.getId(), upToDate, context).isBlockChange()) {
             throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION, ACTION_CHANGE, TYPE_SUBSCRIPTION, blockable.getId().toString());
-        } else if (blockable instanceof SubscriptionBaseBundle && getBlockedStateBundle((SubscriptionBaseBundle) blockable, upToDate, context).isBlockChange()) {
+        } else if (blockable instanceof SubscriptionBaseBundle && getBlockedStateBundle(((SubscriptionBaseBundle) blockable).getAccountId(), blockable.getId(), upToDate, context).isBlockChange()) {
             throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION, ACTION_CHANGE, TYPE_BUNDLE, blockable.getId().toString());
         } else if (blockable instanceof Account && getBlockedStateAccount((Account) blockable, upToDate, context).isBlockChange()) {
             throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION, ACTION_CHANGE, TYPE_ACCOUNT, blockable.getId().toString());
@@ -231,9 +228,9 @@ public class DefaultBlockingChecker implements BlockingChecker {
 
     @Override
     public void checkBlockedEntitlement(final Blockable blockable, final DateTime upToDate, final InternalTenantContext context) throws BlockingApiException {
-        if (blockable instanceof SubscriptionBase && getBlockedStateSubscription((SubscriptionBase) blockable, upToDate, context).isBlockEntitlement()) {
+        if (blockable instanceof SubscriptionBase && getBlockedStateSubscription(((SubscriptionBase) blockable).getBundleId(), blockable.getId(), upToDate, context).isBlockEntitlement()) {
             throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION, ACTION_ENTITLEMENT, TYPE_SUBSCRIPTION, blockable.getId().toString());
-        } else if (blockable instanceof SubscriptionBaseBundle && getBlockedStateBundle((SubscriptionBaseBundle) blockable, upToDate, context).isBlockEntitlement()) {
+        } else if (blockable instanceof SubscriptionBaseBundle && getBlockedStateBundle(((SubscriptionBaseBundle) blockable).getAccountId(), blockable.getId(), upToDate, context).isBlockEntitlement()) {
             throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION, ACTION_ENTITLEMENT, TYPE_BUNDLE, blockable.getId().toString());
         } else if (blockable instanceof Account && getBlockedStateAccount((Account) blockable, upToDate, context).isBlockEntitlement()) {
             throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION, ACTION_ENTITLEMENT, TYPE_ACCOUNT, blockable.getId().toString());
@@ -242,9 +239,9 @@ public class DefaultBlockingChecker implements BlockingChecker {
 
     @Override
     public void checkBlockedBilling(final Blockable blockable, final DateTime upToDate, final InternalTenantContext context) throws BlockingApiException {
-        if (blockable instanceof SubscriptionBase && getBlockedStateSubscription((SubscriptionBase) blockable, upToDate, context).isBlockBilling()) {
+        if (blockable instanceof SubscriptionBase && getBlockedStateSubscription(((SubscriptionBase) blockable).getBundleId(), blockable.getId(), upToDate, context).isBlockBilling()) {
             throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION, ACTION_BILLING, TYPE_SUBSCRIPTION, blockable.getId().toString());
-        } else if (blockable instanceof SubscriptionBaseBundle && getBlockedStateBundle((SubscriptionBaseBundle) blockable, upToDate, context).isBlockBilling()) {
+        } else if (blockable instanceof SubscriptionBaseBundle && getBlockedStateBundle(((SubscriptionBaseBundle) blockable).getAccountId(), blockable.getId(), upToDate, context).isBlockBilling()) {
             throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION, ACTION_BILLING, TYPE_BUNDLE, blockable.getId().toString());
         } else if (blockable instanceof Account && getBlockedStateAccount((Account) blockable, upToDate, context).isBlockBilling()) {
             throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION, ACTION_BILLING, TYPE_ACCOUNT, blockable.getId().toString());
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EntitlementUtils.java b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EntitlementUtils.java
index fd44b4d..5307695 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EntitlementUtils.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EntitlementUtils.java
@@ -78,7 +78,7 @@ public class EntitlementUtils {
         // We only need the bundle id in case of subscriptions (at the account level, we don't need it and at the bundle level, we already have it)
         if (state.getType() == BlockingStateType.SUBSCRIPTION) {
             try {
-                bundleId = subscriptionBaseInternalApi.getSubscriptionFromId(state.getBlockedId(), context).getBundleId();
+                bundleId = subscriptionBaseInternalApi.getBundleIdFromSubscriptionId(state.getBlockedId(), context);
             } catch (final SubscriptionBaseApiException e) {
                 throw new RuntimeException(e);
             }
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/block/TestBlockingChecker.java b/entitlement/src/test/java/org/killbill/billing/entitlement/block/TestBlockingChecker.java
index bb480fa..e8ac66b 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/block/TestBlockingChecker.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/block/TestBlockingChecker.java
@@ -64,6 +64,8 @@ public class TestBlockingChecker extends EntitlementTestSuiteNoDB {
 
         try {
             Mockito.when(subscriptionInternalApi.getBundleFromId(Mockito.<UUID>any(), Mockito.<InternalTenantContext>any())).thenReturn(bundle);
+            Mockito.when(subscriptionInternalApi.getAccountIdFromBundleId(Mockito.<UUID>any(), Mockito.<InternalTenantContext>any())).thenReturn(accountId);
+            Mockito.when(subscriptionInternalApi.getBundleIdFromSubscriptionId(Mockito.<UUID>any(), Mockito.<InternalTenantContext>any())).thenReturn(bundleId);
         } catch (SubscriptionBaseApiException e) {
             Assert.fail(e.toString());
         }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDateNotifier.java b/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDateNotifier.java
index 98ee28a..79949d9 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDateNotifier.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDateNotifier.java
@@ -23,9 +23,7 @@ import java.util.UUID;
 import org.joda.time.DateTime;
 import org.killbill.billing.invoice.InvoiceListener;
 import org.killbill.billing.invoice.api.DefaultInvoiceService;
-import org.killbill.billing.subscription.api.SubscriptionBase;
 import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
-import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.clock.Clock;
 import org.killbill.notificationq.api.NotificationEvent;
@@ -84,21 +82,12 @@ public class DefaultNextBillingDateNotifier extends RetryableService implements 
                 // Just to ensure compatibility with json that might not have that targetDate field (old versions < 0.13.6)
                 final DateTime targetDate = key.getTargetDate() != null ? key.getTargetDate() : eventDate;
                 final UUID firstSubscriptionId = key.getUuidKeys().iterator().next();
-                try {
-                    final SubscriptionBase subscription = subscriptionApi.getSubscriptionFromId(firstSubscriptionId, internalCallContextFactory.createInternalTenantContext(tenantRecordId, accountRecordId));
-                    if (subscription == null) {
-                        log.warn("Unable to retrieve subscriptionId='{}' for event {}", firstSubscriptionId, key);
-                        return;
-                    }
-                    if (key.isDryRunForInvoiceNotification() != null && // Just to ensure compatibility with json that might not have that field (old versions < 0.13.6)
-                        key.isDryRunForInvoiceNotification()) {
-                        processEventForInvoiceNotification(firstSubscriptionId, targetDate, userToken, accountRecordId, tenantRecordId);
-                    } else {
-                        final boolean isRescheduled = key.isRescheduled() == Boolean.TRUE; // Handle null value (old versions < 0.19.7)
-                        processEventForInvoiceGeneration(firstSubscriptionId, targetDate, isRescheduled, userToken, accountRecordId, tenantRecordId);
-                    }
-                } catch (final SubscriptionBaseApiException e) {
-                    log.warn("Error retrieving subscriptionId='{}'", firstSubscriptionId, e);
+                if (key.isDryRunForInvoiceNotification() != null && // Just to ensure compatibility with json that might not have that field (old versions < 0.13.6)
+                    key.isDryRunForInvoiceNotification()) {
+                    processEventForInvoiceNotification(firstSubscriptionId, targetDate, userToken, accountRecordId, tenantRecordId);
+                } else {
+                    final boolean isRescheduled = key.isRescheduled() == Boolean.TRUE; // Handle null value (old versions < 0.19.7)
+                    processEventForInvoiceGeneration(firstSubscriptionId, targetDate, isRescheduled, userToken, accountRecordId, tenantRecordId);
                 }
             }
         };
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
index c6f4c72..727f476 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
@@ -79,6 +79,12 @@ import org.killbill.billing.subscription.events.bcd.BCDEventData;
 import org.killbill.billing.subscription.exceptions.SubscriptionBaseError;
 import org.killbill.billing.util.UUIDs;
 import org.killbill.billing.util.bcd.BillCycleDayCalculator;
+import org.killbill.billing.util.cache.AccountIdFromBundleIdCacheLoader;
+import org.killbill.billing.util.cache.BundleIdFromSubscriptionIdCacheLoader;
+import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheController;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
+import org.killbill.billing.util.cache.CacheLoaderArgument;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.TenantContext;
@@ -107,6 +113,8 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
     private final AddonUtils addonUtils;
     private final InternalCallContextFactory internalCallContextFactory;
     private final CatalogInternalApi catalogInternalApi;
+    private final CacheController<UUID, UUID> accountIdCacheController;
+    private final CacheController<UUID, UUID> bundleIdCacheController;
 
     public static final Comparator<SubscriptionBase> SUBSCRIPTIONS_COMPARATOR = new Comparator<SubscriptionBase>() {
 
@@ -129,11 +137,14 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
                                           final Clock clock,
                                           final CatalogInternalApi catalogInternalApi,
                                           final AddonUtils addonUtils,
+                                          final CacheControllerDispatcher cacheControllerDispatcher,
                                           final InternalCallContextFactory internalCallContextFactory) {
         super(dao, apiService, clock);
         this.addonUtils = addonUtils;
         this.internalCallContextFactory = internalCallContextFactory;
         this.catalogInternalApi = catalogInternalApi;
+        this.accountIdCacheController = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_ID_FROM_BUNDLE_ID);
+        this.bundleIdCacheController = cacheControllerDispatcher.getCacheController(CacheType.BUNDLE_ID_FROM_SUBSCRIPTION_ID);
     }
 
     private List<SubscriptionSpecifier> verifyAndBuildSubscriptionSpecifiers(final SubscriptionBaseBundle bundle,
@@ -317,7 +328,13 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
                 subscriptionAndAddOns.add(subscriptionAndAddOnsSpecifier);
             }
 
-            return apiService.createPlansWithAddOns(accountId, subscriptionAndAddOns, catalog, callContext);
+            final List<SubscriptionBaseWithAddOns> subscriptionBaseWithAddOns = apiService.createPlansWithAddOns(accountId, subscriptionAndAddOns, catalog, callContext);
+            for (final SubscriptionBaseWithAddOns subscriptionBaseWithAO : subscriptionBaseWithAddOns) {
+                for (final SubscriptionBase subscriptionBase : subscriptionBaseWithAO.getSubscriptionBaseList()) {
+                    bundleIdCacheController.putIfAbsent(subscriptionBase.getId(), subscriptionBaseWithAO.getBundle().getId());
+                }
+            }
+            return subscriptionBaseWithAddOns;
         } catch (final CatalogApiException e) {
             throw new SubscriptionBaseApiException(e);
         }
@@ -378,7 +395,9 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
         }
         try {
             final Catalog catalog = catalogInternalApi.getFullCatalog(true, true, context);
-            return dao.createSubscriptionBundle(bundle, catalog, renameCancelledBundleIfExist, context);
+            final SubscriptionBaseBundle subscriptionBundle = dao.createSubscriptionBundle(bundle, catalog, renameCancelledBundleIfExist, context);
+            accountIdCacheController.putIfAbsent(bundle.getId(), accountId);
+            return subscriptionBundle;
         } catch (final CatalogApiException e) {
             throw new  SubscriptionBaseApiException(e);
         }
@@ -544,11 +563,6 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
     }
 
     @Override
-    public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId, final InternalTenantContext context) throws SubscriptionBaseApiException {
-        return dao.getAccountIdFromSubscriptionId(subscriptionId, context);
-    }
-
-    @Override
     public void setChargedThroughDate(final UUID subscriptionId, final DateTime chargedThruDate, final InternalCallContext context) throws SubscriptionBaseApiException {
         try {
 
@@ -790,6 +804,67 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
         }
     }
 
+    @Override
+    public UUID getAccountIdFromBundleId(final UUID bundleId, final InternalTenantContext context) throws SubscriptionBaseApiException {
+        final CacheLoaderArgument arg = createAccountIdFromBundleIdCacheLoaderArgument(context);
+        return accountIdCacheController.get(bundleId, arg);
+    }
+
+    @Override
+    public UUID getBundleIdFromSubscriptionId(final UUID subscriptionId, final InternalTenantContext context) throws SubscriptionBaseApiException {
+        final CacheLoaderArgument arg = createBundleIdFromSubscriptionIdCacheLoaderArgument(context);
+        return bundleIdCacheController.get(subscriptionId, arg);
+    }
+
+    @Override
+    public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId, final InternalTenantContext context) throws SubscriptionBaseApiException {
+        final UUID bundleId = getBundleIdFromSubscriptionId(subscriptionId, context);
+        if (bundleId == null) {
+            throw new SubscriptionBaseApiException(ErrorCode.SUB_GET_NO_BUNDLE_FOR_SUBSCRIPTION, subscriptionId);
+        }
+        final UUID accountId = getAccountIdFromBundleId(bundleId, context);
+        if (accountId == null) {
+            throw new SubscriptionBaseApiException(ErrorCode.SUB_GET_INVALID_BUNDLE_ID, bundleId);
+        }
+        return accountId;
+    }
+
+    private CacheLoaderArgument createAccountIdFromBundleIdCacheLoaderArgument(final InternalTenantContext internalTenantContext) {
+        final AccountIdFromBundleIdCacheLoader.LoaderCallback loaderCallback = new AccountIdFromBundleIdCacheLoader.LoaderCallback() {
+            public UUID loadAccountId(final UUID bundleId, final InternalTenantContext internalTenantContext) {
+                final SubscriptionBaseBundle bundle;
+                try {
+                    bundle = getBundleFromId(bundleId, internalTenantContext);
+                } catch (final SubscriptionBaseApiException e) {
+                    log.warn("Unable to retrieve bundle for id='{}'", bundleId);
+                    return null;
+                }
+                return bundle.getAccountId();
+            }
+        };
+
+        final Object[] args = {loaderCallback};
+        return new CacheLoaderArgument(null, args, internalTenantContext);
+    }
+
+    private CacheLoaderArgument createBundleIdFromSubscriptionIdCacheLoaderArgument(final InternalTenantContext internalTenantContext) {
+        final BundleIdFromSubscriptionIdCacheLoader.LoaderCallback loaderCallback = new BundleIdFromSubscriptionIdCacheLoader.LoaderCallback() {
+            public UUID loadBundleId(final UUID subscriptionId, final InternalTenantContext internalTenantContext) {
+                final SubscriptionBase subscriptionBase;
+                try {
+                    subscriptionBase = getSubscriptionFromId(subscriptionId, internalTenantContext);
+                } catch (final SubscriptionBaseApiException e) {
+                    log.warn("Unable to retrieve subscription for id='{}'", subscriptionId);
+                    return null;
+                }
+                return subscriptionBase.getBundleId();
+            }
+        };
+
+        final Object[] args = {loaderCallback};
+        return new CacheLoaderArgument(null, args, internalTenantContext);
+    }
+
     @VisibleForTesting
     DateTime getEffectiveDateForNewBCD(final int bcd, @Nullable final LocalDate effectiveFromDate, final InternalCallContext internalCallContext) {
         if (internalCallContext.getAccountRecordId() == null) {
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
index f7c8fd0..1c11dae 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
@@ -343,34 +343,6 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
     }
 
     @Override
-    public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId, final InternalTenantContext context) {
-
-        return transactionalSqlDao.execute(true, new EntitySqlDaoTransactionWrapper<UUID>() {
-            @Override
-            public UUID inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
-                final SubscriptionModelDao subscriptionModel = entitySqlDaoWrapperFactory.become(SubscriptionSqlDao.class).getById(subscriptionId.toString(), context);
-                if (subscriptionModel == null) {
-                    log.warn(String.format(ErrorCode.SUB_INVALID_SUBSCRIPTION_ID.getFormat(), subscriptionId.toString()));
-                    return null;
-                }
-
-                final UUID bundleId = subscriptionModel.getBundleId();
-                if (bundleId == null) {
-                    log.warn(String.format(ErrorCode.SUB_GET_NO_BUNDLE_FOR_SUBSCRIPTION.getFormat(), subscriptionId.toString()));
-                    return null;
-                }
-
-                final SubscriptionBundleModelDao bundleModel = entitySqlDaoWrapperFactory.become(BundleSqlDao.class).getById(bundleId.toString(), context);
-                if (bundleModel == null) {
-                    log.warn(String.format(ErrorCode.SUB_GET_INVALID_BUNDLE_ID.getFormat(), bundleId.toString()));
-                    return null;
-                }
-                return bundleModel.getAccountId();
-            }
-        });
-    }
-
-    @Override
     public SubscriptionBase getBaseSubscription(final UUID bundleId, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
         return getBaseSubscription(bundleId, true, catalog, context);
     }
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java
index 8a86d46..3e6dd6c 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java
@@ -59,9 +59,6 @@ public interface SubscriptionDao extends EntityDao<SubscriptionBundleModelDao, S
 
     public SubscriptionBase getSubscriptionFromId(UUID subscriptionId, final Catalog catalog, InternalTenantContext context) throws CatalogApiException;
 
-    // ACCOUNT retrieval
-    public UUID getAccountIdFromSubscriptionId(UUID subscriptionId, InternalTenantContext context);
-
     // SubscriptionBase retrieval
     public SubscriptionBase getBaseSubscription(UUID bundleId, final Catalog catalog, InternalTenantContext context) throws CatalogApiException;
 
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
index ed8e8c2..19dbef1 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
@@ -198,11 +198,6 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
     }
 
     @Override
-    public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId, final InternalTenantContext context) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
     public List<SubscriptionBaseEvent> createSubscriptionsWithAddOns(final List<SubscriptionBaseWithAddOns> subscriptions,
                                                                      final Map<UUID, List<SubscriptionBaseEvent>> initialEventsMap,
                                                                      final Catalog catalog,
diff --git a/util/src/main/java/org/killbill/billing/util/cache/AccountIdFromBundleIdCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/AccountIdFromBundleIdCacheLoader.java
new file mode 100644
index 0000000..9dd2f3a
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/cache/AccountIdFromBundleIdCacheLoader.java
@@ -0,0 +1,50 @@
+/*
+ * 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
+ * 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.
+ */
+
+package org.killbill.billing.util.cache;
+
+import java.util.UUID;
+
+import javax.inject.Singleton;
+
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.util.cache.Cachable.CacheType;
+
+@Singleton
+public class AccountIdFromBundleIdCacheLoader extends BaseCacheLoader<UUID, UUID> {
+
+    @Override
+    public CacheType getCacheType() {
+        return CacheType.ACCOUNT_ID_FROM_BUNDLE_ID;
+    }
+
+    @Override
+    public UUID compute(final UUID key, final CacheLoaderArgument cacheLoaderArgument) {
+        if (cacheLoaderArgument.getArgs() == null ||
+            !(cacheLoaderArgument.getArgs()[0] instanceof LoaderCallback)) {
+            throw new IllegalArgumentException("Missing LoaderCallback from the arguments ");
+        }
+
+        final LoaderCallback callback = (LoaderCallback) cacheLoaderArgument.getArgs()[0];
+        return callback.loadAccountId(key, cacheLoaderArgument.getInternalTenantContext());
+    }
+
+    public interface LoaderCallback {
+
+        UUID loadAccountId(final UUID bundleId, final InternalTenantContext context);
+    }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/BundleIdFromSubscriptionIdCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/BundleIdFromSubscriptionIdCacheLoader.java
new file mode 100644
index 0000000..b88d7b7
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/cache/BundleIdFromSubscriptionIdCacheLoader.java
@@ -0,0 +1,50 @@
+/*
+ * 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
+ * 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.
+ */
+
+package org.killbill.billing.util.cache;
+
+import java.util.UUID;
+
+import javax.inject.Singleton;
+
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.util.cache.Cachable.CacheType;
+
+@Singleton
+public class BundleIdFromSubscriptionIdCacheLoader extends BaseCacheLoader<UUID, UUID> {
+
+    @Override
+    public CacheType getCacheType() {
+        return CacheType.BUNDLE_ID_FROM_SUBSCRIPTION_ID;
+    }
+
+    @Override
+    public UUID compute(final UUID key, final CacheLoaderArgument cacheLoaderArgument) {
+        if (cacheLoaderArgument.getArgs() == null ||
+            !(cacheLoaderArgument.getArgs()[0] instanceof LoaderCallback)) {
+            throw new IllegalArgumentException("Missing LoaderCallback from the arguments ");
+        }
+
+        final LoaderCallback callback = (LoaderCallback) cacheLoaderArgument.getArgs()[0];
+        return callback.loadBundleId(key, cacheLoaderArgument.getInternalTenantContext());
+    }
+
+    public interface LoaderCallback {
+
+        UUID loadBundleId(final UUID subscriptionId, final InternalTenantContext context);
+    }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/Cachable.java b/util/src/main/java/org/killbill/billing/util/cache/Cachable.java
index 97d5cb6..240ff3c 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/Cachable.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/Cachable.java
@@ -50,6 +50,8 @@ public @interface Cachable {
     String OVERRIDDEN_PLAN_CACHE_NAME = "overridden-plan";
     String ACCOUNT_IMMUTABLE_CACHE_NAME = "account-immutable";
     String ACCOUNT_BCD_CACHE_NAME = "account-bcd";
+    String ACCOUNT_ID_FROM_BUNDLE_ID_CACHE_NAME = "account-id-from-bundle-id";
+    String BUNDLE_ID_FROM_SUBSCRIPTION_ID_CACHE_NAME = "bundle-id-from-subscription-id";
 
     CacheType value();
 
@@ -99,7 +101,13 @@ public @interface Cachable {
         ACCOUNT_IMMUTABLE(ACCOUNT_IMMUTABLE_CACHE_NAME, Long.class, ImmutableAccountData.class, false),
 
         /* Account BCD config cache */
-        ACCOUNT_BCD(ACCOUNT_BCD_CACHE_NAME, UUID.class, Integer.class, false);
+        ACCOUNT_BCD(ACCOUNT_BCD_CACHE_NAME, UUID.class, Integer.class, false),
+
+        /* Bundle id to Account id cache */
+        ACCOUNT_ID_FROM_BUNDLE_ID(ACCOUNT_ID_FROM_BUNDLE_ID_CACHE_NAME, UUID.class, UUID.class, false),
+
+        /* Entitlement id to Bundle id cache */
+        BUNDLE_ID_FROM_SUBSCRIPTION_ID(BUNDLE_ID_FROM_SUBSCRIPTION_ID_CACHE_NAME, UUID.class, UUID.class, false);
 
         private final String cacheName;
         private final Class keyType;
diff --git a/util/src/main/java/org/killbill/billing/util/glue/CacheModule.java b/util/src/main/java/org/killbill/billing/util/glue/CacheModule.java
index d4ddf25..cb7c571 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/CacheModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/CacheModule.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
@@ -22,10 +22,12 @@ import javax.cache.CacheManager;
 
 import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.cache.AccountBCDCacheLoader;
+import org.killbill.billing.util.cache.AccountIdFromBundleIdCacheLoader;
 import org.killbill.billing.util.cache.AccountRecordIdCacheLoader;
 import org.killbill.billing.util.cache.AuditLogCacheLoader;
 import org.killbill.billing.util.cache.AuditLogViaHistoryCacheLoader;
 import org.killbill.billing.util.cache.BaseCacheLoader;
+import org.killbill.billing.util.cache.BundleIdFromSubscriptionIdCacheLoader;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.cache.CacheControllerDispatcherProvider;
 import org.killbill.billing.util.cache.ImmutableAccountCacheLoader;
@@ -77,5 +79,7 @@ public class CacheModule extends KillBillModule {
         resultSetMapperSetBinder.addBinding().to(TenantCacheLoader.class).asEagerSingleton();
         resultSetMapperSetBinder.addBinding().to(OverriddenPlanCacheLoader.class).asEagerSingleton();
         resultSetMapperSetBinder.addBinding().to(TenantStateMachineConfigCacheLoader.class).asEagerSingleton();
+        resultSetMapperSetBinder.addBinding().to(AccountIdFromBundleIdCacheLoader.class).asEagerSingleton();
+        resultSetMapperSetBinder.addBinding().to(BundleIdFromSubscriptionIdCacheLoader.class).asEagerSingleton();
     }
 }