killbill-memoizeit

Introduce 2 new ehcaches to take care of immutable account data

10/2/2015 5:01:13 PM

Changes

Details

diff --git a/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java b/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java
index 3b918f6..41791b6 100644
--- a/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java
+++ b/account/src/main/java/org/killbill/billing/account/api/svcs/DefaultAccountInternalApi.java
@@ -22,52 +22,72 @@ import java.util.UUID;
 import javax.inject.Inject;
 
 import org.killbill.billing.ErrorCode;
+import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountApiException;
 import org.killbill.billing.account.api.AccountEmail;
 import org.killbill.billing.account.api.AccountInternalApi;
-import org.killbill.billing.account.api.DefaultAccount;
 import org.killbill.billing.account.api.DefaultAccountEmail;
 import org.killbill.billing.account.api.DefaultImmutableAccountData;
+import org.killbill.billing.account.api.DefaultMutableAccountData;
 import org.killbill.billing.account.api.ImmutableAccountData;
 import org.killbill.billing.account.api.MutableAccountData;
+import org.killbill.billing.account.api.user.DefaultAccountApiBase;
 import org.killbill.billing.account.dao.AccountDao;
 import org.killbill.billing.account.dao.AccountEmailModelDao;
 import org.killbill.billing.account.dao.AccountModelDao;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.util.cache.AccountBCDCacheLoader;
+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.cache.ImmutableAccountCacheLoader.LoaderCallback;
+import org.killbill.billing.util.dao.NonEntityDao;
 
 import com.google.common.base.Function;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableList;
 
-public class DefaultAccountInternalApi implements AccountInternalApi {
+public class DefaultAccountInternalApi extends DefaultAccountApiBase implements AccountInternalApi {
 
     private final AccountDao accountDao;
+    private final CacheControllerDispatcher cacheControllerDispatcher;
+    private final CacheController accountCacheController;
+    private final CacheController bcdCacheController;
+    private final NonEntityDao nonEntityDao;
 
     @Inject
-    public DefaultAccountInternalApi(final AccountDao accountDao) {
+    public DefaultAccountInternalApi(final AccountDao accountDao,
+                                     final NonEntityDao nonEntityDao,
+                                     final CacheControllerDispatcher cacheControllerDispatcher) {
+        super(accountDao, nonEntityDao, cacheControllerDispatcher);
         this.accountDao = accountDao;
+        this.nonEntityDao = nonEntityDao;
+        this.cacheControllerDispatcher = cacheControllerDispatcher;
+        this.accountCacheController = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_IMMUTABLE);
+        this.bcdCacheController = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_BCD);
     }
 
     @Override
     public Account getAccountById(final UUID accountId, final InternalTenantContext context) throws AccountApiException {
-        final AccountModelDao account = accountDao.getById(accountId, context);
-        if (account == null) {
-            throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID, accountId);
-        }
-        return new DefaultAccount(account);
+        return super.getAccountById(accountId, context);
+    }
+
+    @Override
+    public Account getAccountByKey(final String key, final InternalTenantContext context) throws AccountApiException {
+        return super.getAccountByKey(key, context);
     }
 
     @Override
     public Account getAccountByRecordId(final Long recordId, final InternalTenantContext context) throws AccountApiException {
-        final AccountModelDao accountModelDao = getAccountModelDaoByRecordId(recordId, context);
-        return new DefaultAccount(accountModelDao);
+        return super.getAccountByRecordId(recordId, context);
     }
 
     @Override
     public void updateBCD(final String externalKey, final int bcd,
-                              final InternalCallContext context) throws AccountApiException {
+                          final InternalCallContext context) throws AccountApiException {
         final Account currentAccount = getAccountByKey(externalKey, context);
         if (currentAccount == null) {
             throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_KEY, externalKey);
@@ -76,13 +96,15 @@ public class DefaultAccountInternalApi implements AccountInternalApi {
         final MutableAccountData mutableAccountData = currentAccount.toMutableAccountData();
         mutableAccountData.setBillCycleDayLocal(bcd);
         final AccountModelDao accountToUpdate = new AccountModelDao(currentAccount.getId(), mutableAccountData);
+        bcdCacheController.putIfAbsent(currentAccount.getId(), new Integer(bcd));
         accountDao.update(accountToUpdate, context);
     }
 
     @Override
     public int getBCD(final UUID accountId, final InternalTenantContext context) throws AccountApiException {
-        final Account account = getAccountById(accountId, context);
-        return account.getBillCycleDayLocal();
+        final CacheLoaderArgument arg = createBCDCacheLoaderArgument(context);
+        final Integer result = (Integer) bcdCacheController.get(accountId, arg);
+        return result != null ? result : DefaultMutableAccountData.DEFAULT_BILLING_CYCLE_DAY_LOCAL;
     }
 
     @Override
@@ -98,15 +120,6 @@ public class DefaultAccountInternalApi implements AccountInternalApi {
     }
 
     @Override
-    public Account getAccountByKey(final String key, final InternalTenantContext context) throws AccountApiException {
-        final AccountModelDao accountModelDao = accountDao.getAccountByKey(key, context);
-        if (accountModelDao == null) {
-            throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_KEY, key);
-        }
-        return new DefaultAccount(accountModelDao);
-    }
-
-    @Override
     public void removePaymentMethod(final UUID accountId, final InternalCallContext context) throws AccountApiException {
         updatePaymentMethod(accountId, null, context);
     }
@@ -125,20 +138,14 @@ public class DefaultAccountInternalApi implements AccountInternalApi {
 
     @Override
     public ImmutableAccountData getImmutableAccountDataById(final UUID accountId, final InternalTenantContext context) throws AccountApiException {
-        final Account account = getAccountById(accountId, context);
-        return new DefaultImmutableAccountData(account);
-    }
-
-    @Override
-    public ImmutableAccountData getImmutableAccountDataByKey(final String key, final InternalTenantContext context) throws AccountApiException {
-        final Account account = getAccountByKey(key, context);
-        return new DefaultImmutableAccountData(account);
+        final Long recordId = nonEntityDao.retrieveRecordIdFromObject(accountId, ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.RECORD_ID));
+        return getImmutableAccountDataByRecordId(recordId, context);
     }
 
     @Override
     public ImmutableAccountData getImmutableAccountDataByRecordId(final Long recordId, final InternalTenantContext context) throws AccountApiException {
-        final Account account = getAccountByRecordId(recordId, context);
-        return new DefaultImmutableAccountData(account);
+        final CacheLoaderArgument arg = createImmutableAccountCacheLoaderArgument(context);
+        return (ImmutableAccountData) accountCacheController.get(recordId, arg);
     }
 
     private AccountModelDao getAccountModelDaoByRecordId(final Long recordId, final InternalTenantContext context) throws AccountApiException {
@@ -149,5 +156,36 @@ public class DefaultAccountInternalApi implements AccountInternalApi {
         return accountModelDao;
     }
 
+    private int getBCDInternal(final UUID accountId, final InternalTenantContext context) {
+        final Long bcd = accountDao.getAccountBCD(accountId, context);
+        return bcd != null ? bcd.intValue() : DefaultMutableAccountData.DEFAULT_BILLING_CYCLE_DAY_LOCAL;
+    }
+
+    private CacheLoaderArgument createImmutableAccountCacheLoaderArgument(final InternalTenantContext context) {
+        final LoaderCallback loaderCallback = new LoaderCallback() {
+            @Override
+            public Object loadAccount(final Long recordId, final InternalTenantContext context) {
+                final Account account = getAccountByRecordIdInternal(recordId, context);
+                return account != null ? new DefaultImmutableAccountData(account) : null;
+            }
+        };
+        final Object[] args = new Object[1];
+        args[0] = loaderCallback;
+        final ObjectType irrelevant = null;
+        return new CacheLoaderArgument(irrelevant, args, context);
+    }
 
+    private CacheLoaderArgument createBCDCacheLoaderArgument(final InternalTenantContext context) {
+        final AccountBCDCacheLoader.LoaderCallback loaderCallback = new AccountBCDCacheLoader.LoaderCallback() {
+            @Override
+            public Object loadAccountBCD(final UUID accountId, final InternalTenantContext context) {
+                int bcd = getBCDInternal(accountId, context);
+                return new Integer(bcd);
+            }
+        };
+        final Object[] args = new Object[1];
+        args[0] = loaderCallback;
+        final ObjectType irrelevant = null;
+        return new CacheLoaderArgument(irrelevant, args, context);
+    }
 }
diff --git a/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountApiBase.java b/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountApiBase.java
new file mode 100644
index 0000000..7792ab4
--- /dev/null
+++ b/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountApiBase.java
@@ -0,0 +1,84 @@
+/*
+ * 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
+ * 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.account.api.user;
+
+import java.util.UUID;
+
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.ObjectType;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.account.api.AccountApiException;
+import org.killbill.billing.account.api.DefaultAccount;
+import org.killbill.billing.account.api.DefaultImmutableAccountData;
+import org.killbill.billing.account.dao.AccountDao;
+import org.killbill.billing.account.dao.AccountModelDao;
+import org.killbill.billing.callcontext.InternalTenantContext;
+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.dao.NonEntityDao;
+
+public class DefaultAccountApiBase {
+
+    private final AccountDao accountDao;
+    private final CacheControllerDispatcher cacheControllerDispatcher;
+    private final CacheController accountCacheController;
+    private final NonEntityDao nonEntityDao;
+
+    public DefaultAccountApiBase(final AccountDao accountDao,
+                                 final NonEntityDao nonEntityDao,
+                                 final CacheControllerDispatcher cacheControllerDispatcher) {
+        this.accountDao = accountDao;
+        this.nonEntityDao = nonEntityDao;
+        this.cacheControllerDispatcher = cacheControllerDispatcher;
+        this.accountCacheController = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_IMMUTABLE);
+    }
+
+    protected Account getAccountById(final UUID accountId, final InternalTenantContext context) throws AccountApiException {
+        final Long recordId = nonEntityDao.retrieveRecordIdFromObject(accountId, ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.RECORD_ID));
+        final Account account = getAccountByRecordIdInternal(recordId, context);
+        if (account == null) {
+            throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID, accountId);
+        }
+        accountCacheController.putIfAbsent(accountId, new DefaultImmutableAccountData(account));
+        return account;
+    }
+
+    protected Account getAccountByKey(final String key, final InternalTenantContext context) throws AccountApiException {
+        final AccountModelDao accountModelDao = accountDao.getAccountByKey(key, context);
+        if (accountModelDao == null) {
+            throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_KEY, key);
+        }
+        final Account account = new DefaultAccount(accountModelDao);
+        accountCacheController.putIfAbsent(account.getId(), new DefaultImmutableAccountData(account));
+        return account;
+    }
+
+    protected Account getAccountByRecordId(final Long recordId, final InternalTenantContext context) throws AccountApiException {
+        final Account account = getAccountByRecordIdInternal(recordId, context);
+        if (account == null) {
+            throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_RECORD_ID, recordId);
+        }
+        return account;
+    }
+
+    protected Account getAccountByRecordIdInternal(final Long recordId, final InternalTenantContext context) {
+        final AccountModelDao accountModelDao = accountDao.getByRecordId(recordId, context);
+        return accountModelDao != null ? new DefaultAccount(accountModelDao) : null;
+    }
+}
diff --git a/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountUserApi.java b/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountUserApi.java
index 55fb71b..b137cc0 100644
--- a/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountUserApi.java
+++ b/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountUserApi.java
@@ -32,9 +32,12 @@ import org.killbill.billing.account.api.DefaultAccountEmail;
 import org.killbill.billing.account.dao.AccountDao;
 import org.killbill.billing.account.dao.AccountEmailModelDao;
 import org.killbill.billing.account.dao.AccountModelDao;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.TenantContext;
+import org.killbill.billing.util.dao.NonEntityDao;
 import org.killbill.billing.util.entity.Pagination;
 import org.killbill.billing.util.entity.dao.DefaultPaginationHelper.SourcePaginationBuilder;
 
@@ -45,17 +48,35 @@ import com.google.inject.Inject;
 
 import static org.killbill.billing.util.entity.dao.DefaultPaginationHelper.getEntityPaginationNoException;
 
-public class DefaultAccountUserApi implements AccountUserApi {
+public class DefaultAccountUserApi extends DefaultAccountApiBase implements AccountUserApi {
 
     private final InternalCallContextFactory internalCallContextFactory;
     private final AccountDao accountDao;
 
     @Inject
-    public DefaultAccountUserApi(final InternalCallContextFactory internalCallContextFactory, final AccountDao accountDao) {
+    public DefaultAccountUserApi(final AccountDao accountDao,
+                                 final NonEntityDao nonEntityDao,
+                                 final CacheControllerDispatcher cacheControllerDispatcher,
+                                 final InternalCallContextFactory internalCallContextFactory) {
+        super(accountDao, nonEntityDao, cacheControllerDispatcher);
         this.internalCallContextFactory = internalCallContextFactory;
         this.accountDao = accountDao;
     }
 
+
+    @Override
+    public Account getAccountByKey(final String key, final TenantContext context) throws AccountApiException {
+        final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(context);
+        return getAccountByKey(key, internalTenantContext);
+    }
+
+    @Override
+    public Account getAccountById(final UUID id, final TenantContext context) throws AccountApiException {
+        final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(context);
+        return getAccountById(id, internalTenantContext);
+    }
+
+
     @Override
     public Account createAccount(final AccountData data, final CallContext context) throws AccountApiException {
         // Not transactional, but there is a db constraint on that column
@@ -69,25 +90,6 @@ public class DefaultAccountUserApi implements AccountUserApi {
         return new DefaultAccount(account);
     }
 
-    @Override
-    public Account getAccountByKey(final String key, final TenantContext context) throws AccountApiException {
-        final AccountModelDao account = accountDao.getAccountByKey(key, internalCallContextFactory.createInternalTenantContext(context));
-        if (account == null) {
-            throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_KEY, key);
-        }
-
-        return new DefaultAccount(account);
-    }
-
-    @Override
-    public Account getAccountById(final UUID id, final TenantContext context) throws AccountApiException {
-        final AccountModelDao account = accountDao.getById(id, internalCallContextFactory.createInternalTenantContext(context));
-        if (account == null) {
-            throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID, id);
-        }
-
-        return new DefaultAccount(account);
-    }
 
     @Override
     public Pagination<Account> searchAccounts(final String searchKey, final Long offset, final Long limit, final TenantContext context) {
diff --git a/account/src/main/java/org/killbill/billing/account/dao/AccountDao.java b/account/src/main/java/org/killbill/billing/account/dao/AccountDao.java
index 2b79ca2..ec9b237 100644
--- a/account/src/main/java/org/killbill/billing/account/dao/AccountDao.java
+++ b/account/src/main/java/org/killbill/billing/account/dao/AccountDao.java
@@ -28,26 +28,28 @@ import org.killbill.billing.util.entity.dao.EntityDao;
 
 public interface AccountDao extends EntityDao<AccountModelDao, Account, AccountApiException> {
 
-    public AccountModelDao getAccountByKey(String key, InternalTenantContext context);
+    AccountModelDao getAccountByKey(String key, InternalTenantContext context);
 
-    public Pagination<AccountModelDao> searchAccounts(String searchKey, Long offset, Long limit, InternalTenantContext context);
+    Pagination<AccountModelDao> searchAccounts(String searchKey, Long offset, Long limit, InternalTenantContext context);
 
     /**
      * @throws AccountApiException when externalKey is null
      */
-    public UUID getIdFromKey(String externalKey, InternalTenantContext context) throws AccountApiException;
+    UUID getIdFromKey(String externalKey, InternalTenantContext context) throws AccountApiException;
 
     /**
      * @param accountId       the id of the account
      * @param paymentMethodId the is of the current default paymentMethod
      */
-    public void updatePaymentMethod(UUID accountId, UUID paymentMethodId, InternalCallContext context) throws AccountApiException;
+    void updatePaymentMethod(UUID accountId, UUID paymentMethodId, InternalCallContext context) throws AccountApiException;
 
-    public void update(AccountModelDao account, InternalCallContext context) throws AccountApiException;
+    void update(AccountModelDao account, InternalCallContext context) throws AccountApiException;
 
-    public void addEmail(AccountEmailModelDao email, InternalCallContext context) throws AccountApiException;
+    void addEmail(AccountEmailModelDao email, InternalCallContext context) throws AccountApiException;
 
-    public void removeEmail(AccountEmailModelDao email, InternalCallContext context);
+    void removeEmail(AccountEmailModelDao email, InternalCallContext context);
 
-    public List<AccountEmailModelDao> getEmailsByAccountId(UUID accountId, InternalTenantContext context);
+    List<AccountEmailModelDao> getEmailsByAccountId(UUID accountId, InternalTenantContext context);
+
+    Long getAccountBCD(UUID accountId, InternalTenantContext context);
 }
diff --git a/account/src/main/java/org/killbill/billing/account/dao/AccountSqlDao.java b/account/src/main/java/org/killbill/billing/account/dao/AccountSqlDao.java
index 8eeff93..b566df9 100644
--- a/account/src/main/java/org/killbill/billing/account/dao/AccountSqlDao.java
+++ b/account/src/main/java/org/killbill/billing/account/dao/AccountSqlDao.java
@@ -18,11 +18,6 @@ package org.killbill.billing.account.dao;
 
 import java.util.UUID;
 
-import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.BindBean;
-import org.skife.jdbi.v2.sqlobject.SqlQuery;
-import org.skife.jdbi.v2.sqlobject.SqlUpdate;
-
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
@@ -30,6 +25,10 @@ import org.killbill.billing.util.audit.ChangeType;
 import org.killbill.billing.util.entity.dao.Audited;
 import org.killbill.billing.util.entity.dao.EntitySqlDao;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
 
 @EntitySqlDaoStringTemplate
 public interface AccountSqlDao extends EntitySqlDao<AccountModelDao, Account> {
@@ -42,6 +41,10 @@ public interface AccountSqlDao extends EntitySqlDao<AccountModelDao, Account> {
     public UUID getIdFromKey(@Bind("externalKey") final String key,
                              @BindBean final InternalTenantContext context);
 
+    @SqlQuery
+    public Long getBCD(@Bind("id") String accountId,
+                       @BindBean final InternalTenantContext context);
+
     @SqlUpdate
     @Audited(ChangeType.UPDATE)
     public void update(@BindBean final AccountModelDao account,
diff --git a/account/src/main/java/org/killbill/billing/account/dao/DefaultAccountDao.java b/account/src/main/java/org/killbill/billing/account/dao/DefaultAccountDao.java
index b14c677..79f7b92 100644
--- a/account/src/main/java/org/killbill/billing/account/dao/DefaultAccountDao.java
+++ b/account/src/main/java/org/killbill/billing/account/dao/DefaultAccountDao.java
@@ -246,4 +246,13 @@ public class DefaultAccountDao extends EntityDaoBase<AccountModelDao, Account, A
         });
     }
 
+    @Override
+    public Long getAccountBCD(final UUID accountId, final InternalTenantContext context) {
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Long>() {
+            @Override
+            public Long inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+                return entitySqlDaoWrapperFactory.become(AccountSqlDao.class).getBCD(accountId.toString(), context);
+            }
+        });
+    }
 }
diff --git a/account/src/main/resources/org/killbill/billing/account/dao/AccountSqlDao.sql.stg b/account/src/main/resources/org/killbill/billing/account/dao/AccountSqlDao.sql.stg
index 0f5bb37..a500028 100644
--- a/account/src/main/resources/org/killbill/billing/account/dao/AccountSqlDao.sql.stg
+++ b/account/src/main/resources/org/killbill/billing/account/dao/AccountSqlDao.sql.stg
@@ -86,6 +86,12 @@ getAccountByKey() ::= <<
     where external_key = :externalKey <AND_CHECK_TENANT()>;
 >>
 
+getBCD() ::= <<
+    select billing_cycle_day_local
+    from accounts
+    where id = :id <AND_CHECK_TENANT()>;
+>>
+
 searchQuery(prefix) ::= <<
      <idField(prefix)> = :searchKey
   or <prefix>name like :likeSearchKey
diff --git a/account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApiWithMocks.java b/account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApiWithMocks.java
index 1843b08..f3d7863 100644
--- a/account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApiWithMocks.java
+++ b/account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApiWithMocks.java
@@ -53,7 +53,7 @@ public class TestDefaultAccountUserApiWithMocks extends AccountTestSuiteNoDB {
     @BeforeMethod(groups = "fast")
     public void setUp() throws Exception {
         accountDao = new MockAccountDao(Mockito.mock(PersistentBus.class));
-        accountUserApi = new DefaultAccountUserApi(internalFactory, accountDao);
+        accountUserApi = new DefaultAccountUserApi(accountDao, nonEntityDao, controllerDispatcher, internalFactory);
     }
 
     @Test(groups = "fast", description = "Test Account create API")
diff --git a/account/src/test/java/org/killbill/billing/account/dao/MockAccountDao.java b/account/src/test/java/org/killbill/billing/account/dao/MockAccountDao.java
index 04a8bbe..d60ef82 100644
--- a/account/src/test/java/org/killbill/billing/account/dao/MockAccountDao.java
+++ b/account/src/test/java/org/killbill/billing/account/dao/MockAccountDao.java
@@ -163,4 +163,10 @@ public class MockAccountDao extends MockEntityDaoBase<AccountModelDao, Account, 
         }));
     }
 
+    @Override
+    public Long getAccountBCD(final UUID accountId, final InternalTenantContext context) {
+        final AccountModelDao account = getById(accountId, context);
+        return account != null ? account.getBillingCycleDayLocal() : 0L;
+    }
+
 }
diff --git a/api/src/main/java/org/killbill/billing/account/api/AccountInternalApi.java b/api/src/main/java/org/killbill/billing/account/api/AccountInternalApi.java
index 9a61b19..4f7a92f 100644
--- a/api/src/main/java/org/killbill/billing/account/api/AccountInternalApi.java
+++ b/api/src/main/java/org/killbill/billing/account/api/AccountInternalApi.java
@@ -44,8 +44,6 @@ public interface AccountInternalApi {
 
     ImmutableAccountData getImmutableAccountDataById(UUID accountId, InternalTenantContext context) throws AccountApiException;
 
-    ImmutableAccountData getImmutableAccountDataByKey(String key, InternalTenantContext context) throws AccountApiException;
-
     ImmutableAccountData getImmutableAccountDataByRecordId(Long recordId, InternalTenantContext context) throws AccountApiException;
 
 }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
index a081939..c962709 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
@@ -243,8 +243,6 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
     @Inject
     protected IDBI idbi;
 
-    @Inject
-    protected CacheControllerDispatcher controlCacheDispatcher;
 
     @Inject
     protected TestApiListener busHandler;
@@ -273,9 +271,6 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
         //Thread.currentThread().setContextClassLoader(null);
 
         log.debug("RESET TEST FRAMEWORK");
-
-        controlCacheDispatcher.clearAll();
-
         overdueConfigCache.loadDefaultOverdueConfig((OverdueConfig) null);
 
         clock.resetDeltaFromReality();
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPublicBus.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPublicBus.java
index 012fa68..6bde96a 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPublicBus.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPublicBus.java
@@ -85,8 +85,6 @@ public class TestPublicBus extends TestIntegrationBase {
 
         log.debug("RESET TEST FRAMEWORK");
 
-        controlCacheDispatcher.clearAll();
-
         overdueConfigCache.loadDefaultOverdueConfig((OverdueConfig) null);
 
         clock.resetDeltaFromReality();
diff --git a/usage/src/test/java/org/killbill/billing/usage/glue/TestUsageModuleWithEmbeddedDB.java b/usage/src/test/java/org/killbill/billing/usage/glue/TestUsageModuleWithEmbeddedDB.java
index 4db4fd8..1e71a18 100644
--- a/usage/src/test/java/org/killbill/billing/usage/glue/TestUsageModuleWithEmbeddedDB.java
+++ b/usage/src/test/java/org/killbill/billing/usage/glue/TestUsageModuleWithEmbeddedDB.java
@@ -20,6 +20,8 @@ package org.killbill.billing.usage.glue;
 
 import org.killbill.billing.GuicyKillbillTestWithEmbeddedDBModule;
 import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.util.glue.CacheModule;
+import org.killbill.billing.util.glue.NonEntityDaoModule;
 
 public class TestUsageModuleWithEmbeddedDB extends TestUsageModule {
 
@@ -32,5 +34,7 @@ public class TestUsageModuleWithEmbeddedDB extends TestUsageModule {
         super.configure();
 
         install(new GuicyKillbillTestWithEmbeddedDBModule(configSource));
+        install(new CacheModule(configSource));
+        install(new NonEntityDaoModule(configSource));
     }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/cache/AccountBCDCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/AccountBCDCacheLoader.java
new file mode 100644
index 0000000..d88ea69
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/cache/AccountBCDCacheLoader.java
@@ -0,0 +1,59 @@
+/*
+ * 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
+ * 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 org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.util.cache.Cachable.CacheType;
+
+public class AccountBCDCacheLoader extends BaseCacheLoader {
+
+    @Override
+    public CacheType getCacheType() {
+        return CacheType.ACCOUNT_BCD;
+    }
+
+    @Override
+    public Object load(final Object key, final Object argument) {
+
+        checkCacheLoaderStatus();
+
+        if (!(key instanceof UUID)) {
+            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 CacheLoaderArgument cacheLoaderArgument = (CacheLoaderArgument) argument;
+
+        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.loadAccountBCD((UUID) key, cacheLoaderArgument.getInternalTenantContext());
+    }
+
+    public interface LoaderCallback {
+        Object loadAccountBCD(final UUID accountId, 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 767cc94..bfc0b27 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
@@ -25,20 +25,22 @@ import java.lang.annotation.Target;
 @Target({ElementType.METHOD})
 public @interface Cachable {
 
-    public final String RECORD_ID_CACHE_NAME = "record-id";
-    public final String ACCOUNT_RECORD_ID_CACHE_NAME = "account-record-id";
-    public final String TENANT_RECORD_ID_CACHE_NAME = "tenant-record-id";
-    public final String OBJECT_ID_CACHE_NAME = "object-id";
-    public final String AUDIT_LOG_CACHE_NAME = "audit-log";
-    public final String AUDIT_LOG_VIA_HISTORY_CACHE_NAME = "audit-log-via-history";
-    public final String TENANT_CATALOG_CACHE_NAME = "tenant-catalog";
-    public final String TENANT_OVERDUE_CONFIG_CACHE_NAME = "tenant-overdue-config";
-    public final String TENANT_KV_CACHE_NAME = "tenant-kv";
-    public final String OVERRIDDEN_PLAN_CACHE_NAME = "overridden-plan";
-
-    public CacheType value();
-
-    public enum CacheType {
+    String RECORD_ID_CACHE_NAME = "record-id";
+    String ACCOUNT_RECORD_ID_CACHE_NAME = "account-record-id";
+    String TENANT_RECORD_ID_CACHE_NAME = "tenant-record-id";
+    String OBJECT_ID_CACHE_NAME = "object-id";
+    String AUDIT_LOG_CACHE_NAME = "audit-log";
+    String AUDIT_LOG_VIA_HISTORY_CACHE_NAME = "audit-log-via-history";
+    String TENANT_CATALOG_CACHE_NAME = "tenant-catalog";
+    String TENANT_OVERDUE_CONFIG_CACHE_NAME = "tenant-overdue-config";
+    String TENANT_KV_CACHE_NAME = "tenant-kv";
+    String OVERRIDDEN_PLAN_CACHE_NAME = "overridden-plan";
+    String ACCOUNT_IMMUTABLE_CACHE_NAME = "account-immutable";
+    String ACCOUNT_BCD_CACHE_NAME = "account-bcd";
+
+    CacheType value();
+
+    enum CacheType {
 
         /* Mapping from object 'id (UUID)' -> object 'recordId (Long' */
         RECORD_ID(RECORD_ID_CACHE_NAME, false),
@@ -68,7 +70,13 @@ public @interface Cachable {
         TENANT_KV(TENANT_KV_CACHE_NAME, false),
 
         /* Overwritten plans  */
-        OVERRIDDEN_PLAN(OVERRIDDEN_PLAN_CACHE_NAME, false);
+        OVERRIDDEN_PLAN(OVERRIDDEN_PLAN_CACHE_NAME, false),
+
+        /* Immutable account data config cache */
+        ACCOUNT_IMMUTABLE(ACCOUNT_IMMUTABLE_CACHE_NAME, false),
+
+        /* Account BCD config cache */
+        ACCOUNT_BCD(ACCOUNT_BCD_CACHE_NAME, false);
 
         private final String cacheName;
         private final boolean isKeyPrefixedWithTableName;
diff --git a/util/src/main/java/org/killbill/billing/util/cache/CacheController.java b/util/src/main/java/org/killbill/billing/util/cache/CacheController.java
index 5743f65..2c6be1e 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/CacheController.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/CacheController.java
@@ -20,13 +20,17 @@ import org.killbill.billing.util.cache.Cachable.CacheType;
 
 public interface CacheController<K, V> {
 
-    public void add(K key, V value);
+    void add(K key, V value);
 
-    public V get(K key, CacheLoaderArgument objectType);
+    V get(K key, CacheLoaderArgument objectType);
 
-    public boolean remove(K key);
+    V get(K key);
 
-    public int size();
+    boolean remove(K key);
+
+    void putIfAbsent(final K key, V value);
+
+    int size();
 
     void removeAll();
 
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 1f7998f..08d04d2 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
@@ -18,6 +18,8 @@
 
 package org.killbill.billing.util.cache;
 
+import javax.annotation.Nullable;
+
 import org.killbill.billing.util.cache.Cachable.CacheType;
 
 import net.sf.ehcache.Ehcache;
@@ -39,12 +41,18 @@ public class EhCacheBasedCacheController<K, V> implements CacheController<K, V> 
     }
 
     @Override
-    public V get(final K key, final CacheLoaderArgument cacheLoaderArgument) {
-        final Element element = cache.getWithLoader(key, null, cacheLoaderArgument);
-        if (element == null || element.getObjectValue() == null || element.getObjectValue().equals(BaseCacheLoader.EMPTY_VALUE_PLACEHOLDER)) {
-            return null;
-        }
-        return (V) element.getObjectValue();
+    public V get(final K key, @Nullable final CacheLoaderArgument cacheLoaderArgument) {
+        return getWithOrWithoutCacheLoaderArgument(key, cacheLoaderArgument);
+    }
+
+    @Override
+    public V get(final K key) {
+        return getWithOrWithoutCacheLoaderArgument(key, null);
+    }
+
+    public void putIfAbsent(final K key, V value) {
+        final Element element = new Element(key, value);
+        cache.putIfAbsent(element);
     }
 
     @Override
@@ -66,4 +74,13 @@ public class EhCacheBasedCacheController<K, V> implements CacheController<K, V> 
     public CacheType getCacheType() {
         return cacheType;
     }
+
+    private V getWithOrWithoutCacheLoaderArgument(final K key, @Nullable final CacheLoaderArgument cacheLoaderArgument) {
+        final Element element = cacheLoaderArgument != null ? cache.getWithLoader(key, null, cacheLoaderArgument) : cache.get(key);
+        if (element == null || element.getObjectValue() == null || element.getObjectValue().equals(BaseCacheLoader.EMPTY_VALUE_PLACEHOLDER)) {
+            return null;
+        }
+        return (V) element.getObjectValue();
+    }
+
 }
diff --git a/util/src/main/java/org/killbill/billing/util/cache/EhCacheCacheManagerProvider.java b/util/src/main/java/org/killbill/billing/util/cache/EhCacheCacheManagerProvider.java
index 42d8966..269ff66 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/EhCacheCacheManagerProvider.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/EhCacheCacheManagerProvider.java
@@ -51,6 +51,8 @@ public class EhCacheCacheManagerProvider implements Provider<CacheManager> {
     @Inject
     public EhCacheCacheManagerProvider(final MetricRegistry metricRegistry,
                                        final CacheConfig cacheConfig,
+                                       final ImmutableAccountCacheLoader accountCacheLoader,
+                                       final AccountBCDCacheLoader accountBCDCacheLoader,
                                        final RecordIdCacheLoader recordIdCacheLoader,
                                        final AccountRecordIdCacheLoader accountRecordIdCacheLoader,
                                        final TenantRecordIdCacheLoader tenantRecordIdCacheLoader,
@@ -63,6 +65,8 @@ public class EhCacheCacheManagerProvider implements Provider<CacheManager> {
                                        final OverriddenPlanCacheLoader overriddenPlanCacheLoader) {
         this.metricRegistry = metricRegistry;
         this.cacheConfig = cacheConfig;
+        cacheLoaders.add(accountCacheLoader);
+        cacheLoaders.add(accountBCDCacheLoader);
         cacheLoaders.add(recordIdCacheLoader);
         cacheLoaders.add(accountRecordIdCacheLoader);
         cacheLoaders.add(tenantRecordIdCacheLoader);
diff --git a/util/src/main/java/org/killbill/billing/util/cache/ImmutableAccountCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/ImmutableAccountCacheLoader.java
new file mode 100644
index 0000000..866c44e
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/cache/ImmutableAccountCacheLoader.java
@@ -0,0 +1,57 @@
+/*
+ * 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
+ * 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 org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.util.cache.Cachable.CacheType;
+
+public class ImmutableAccountCacheLoader extends BaseCacheLoader {
+
+    @Override
+    public CacheType getCacheType() {
+        return CacheType.ACCOUNT_IMMUTABLE;
+    }
+
+    @Override
+    public Object load(final Object key, final Object argument) {
+
+        checkCacheLoaderStatus();
+
+        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 CacheLoaderArgument cacheLoaderArgument = (CacheLoaderArgument) argument;
+
+        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.loadAccount((Long) key, cacheLoaderArgument.getInternalTenantContext());
+    }
+
+    public interface LoaderCallback {
+        Object loadAccount(final Long recordId, final InternalTenantContext context);
+    }
+}
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 6a1accf..897e75d 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
@@ -81,7 +81,6 @@ public class TenantCatalogCacheLoader extends BaseCacheLoader {
     }
 
     public interface LoaderCallback {
-
         public Object loadCatalog(final List<String> catalogXMLs, final Long tenantRecordId) throws CatalogApiException;
     }
 }
diff --git a/util/src/main/resources/ehcache.xml b/util/src/main/resources/ehcache.xml
index 7769245..d172871 100644
--- a/util/src/main/resources/ehcache.xml
+++ b/util/src/main/resources/ehcache.xml
@@ -168,6 +168,32 @@
                 properties=""/>
     </cache>
 
+    <cache name="account-immutable"
+           maxElementsInMemory="1000"
+           maxElementsOnDisk="0"
+           overflowToDisk="false"
+           diskPersistent="false"
+           memoryStoreEvictionPolicy="LFU"
+           statistics="true"
+            >
+        <cacheEventListenerFactory
+                class="org.killbill.billing.util.cache.ExpirationListenerFactory"
+                properties=""/>
+    </cache>
+
+    <cache name="account-bcd"
+           maxElementsInMemory="1000"
+           maxElementsOnDisk="0"
+           overflowToDisk="false"
+           diskPersistent="false"
+           memoryStoreEvictionPolicy="LFU"
+           statistics="true"
+            >
+        <cacheEventListenerFactory
+                class="org.killbill.billing.util.cache.ExpirationListenerFactory"
+                properties=""/>
+    </cache>
+
 
 </ehcache>
 
diff --git a/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuiteWithEmbeddedDB.java b/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuiteWithEmbeddedDB.java
index eac77a7..565027e 100644
--- a/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuiteWithEmbeddedDB.java
+++ b/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuiteWithEmbeddedDB.java
@@ -19,6 +19,7 @@ package org.killbill.billing;
 import javax.inject.Inject;
 import javax.sql.DataSource;
 
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.commons.embeddeddb.EmbeddedDB;
 import org.skife.jdbi.v2.IDBI;
 import org.slf4j.Logger;
@@ -40,6 +41,10 @@ public class GuicyKillbillTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuite
     @Inject
     protected IDBI dbi;
 
+    @Inject
+    protected CacheControllerDispatcher controlCacheDispatcher;
+
+
     @BeforeSuite(groups = "slow")
     public void beforeSuite() throws Exception {
         DBTestingHelper.get().start();
@@ -51,6 +56,7 @@ public class GuicyKillbillTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuite
             DBTestingHelper.get().getInstance().cleanupAllTables();
         } catch (final Exception ignored) {
         }
+        controlCacheDispatcher.clearAll();
     }
 
     @AfterSuite(groups = "slow")
diff --git a/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java b/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java
index caff3f8..df64546 100644
--- a/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java
+++ b/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java
@@ -59,8 +59,6 @@ public abstract class UtilTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuite
     @Inject
     protected PersistentBus eventBus;
     @Inject
-    protected CacheControllerDispatcher controlCacheDispatcher;
-    @Inject
     protected NonEntityDao nonEntityDao;
     @Inject
     protected InternalCallContextFactory internalCallContextFactory;
@@ -112,8 +110,6 @@ public abstract class UtilTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuite
         eventBus.start();
         eventBus.register(eventsListener);
 
-        controlCacheDispatcher.clearAll();
-
         // Make sure we start with a clean state
         assertListenerStatus();
     }