killbill-memoizeit

Implement per overdue config catalog and introduce ehcache

12/19/2014 3:23:58 AM

Changes

beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/MockOverdueService.java 46(+0 -46)

Details

diff --git a/api/src/main/java/org/killbill/billing/glue/OverdueModule.java b/api/src/main/java/org/killbill/billing/glue/OverdueModule.java
index 7dc786b..c10b111 100644
--- a/api/src/main/java/org/killbill/billing/glue/OverdueModule.java
+++ b/api/src/main/java/org/killbill/billing/glue/OverdueModule.java
@@ -18,6 +18,6 @@ package org.killbill.billing.glue;
 
 public interface OverdueModule {
 
-    public abstract void installOverdueUserApi();
+    public void installOverdueUserApi();
 
 }
diff --git a/api/src/main/java/org/killbill/billing/overdue/OverdueService.java b/api/src/main/java/org/killbill/billing/overdue/OverdueService.java
index 59bec21..88b7af0 100644
--- a/api/src/main/java/org/killbill/billing/overdue/OverdueService.java
+++ b/api/src/main/java/org/killbill/billing/overdue/OverdueService.java
@@ -18,13 +18,14 @@
 
 package org.killbill.billing.overdue;
 
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.overdue.api.OverdueApiException;
+import org.killbill.billing.overdue.api.OverdueConfig;
 import org.killbill.billing.platform.api.KillbillService;
 
 public interface OverdueService extends KillbillService {
 
-    String OVERDUE_SERVICE_NAME = "overdue-service";
+    public static final String OVERDUE_SERVICE_NAME = "overdue-service";
 
-    public String getName();
-
-    public OverdueInternalApi getUserApi();
+    public OverdueConfig getOverdueConfig(final InternalTenantContext internalTenantContext) throws OverdueApiException;
 }
diff --git a/api/src/main/java/org/killbill/billing/tenant/api/TenantInternalApi.java b/api/src/main/java/org/killbill/billing/tenant/api/TenantInternalApi.java
index 2b7aee0..c1ff94a 100644
--- a/api/src/main/java/org/killbill/billing/tenant/api/TenantInternalApi.java
+++ b/api/src/main/java/org/killbill/billing/tenant/api/TenantInternalApi.java
@@ -22,5 +22,7 @@ import java.util.List;
 import org.killbill.billing.callcontext.InternalTenantContext;
 
 public interface TenantInternalApi {
-    List<String> getTenantCatalogs(InternalTenantContext tenantContext);
+    public List<String> getTenantCatalogs(InternalTenantContext tenantContext);
+
+    public String getTenantOverdueConfig(InternalTenantContext tenantContext);
 }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/IntegrationTestOverdueModule.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/IntegrationTestOverdueModule.java
index edee13b..510fcd2 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/IntegrationTestOverdueModule.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/IntegrationTestOverdueModule.java
@@ -18,7 +18,6 @@
 
 package org.killbill.billing.beatrix.integration.overdue;
 
-import org.killbill.billing.overdue.OverdueService;
 import org.killbill.billing.overdue.glue.DefaultOverdueModule;
 import org.killbill.billing.platform.api.KillbillConfigSource;
 
@@ -28,7 +27,4 @@ public class IntegrationTestOverdueModule extends DefaultOverdueModule {
         super(configSource);
     }
 
-    protected void installOverdueService() {
-        bind(OverdueService.class).to(MockOverdueService.class);
-    }
 }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueBase.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueBase.java
index c9d3f15..ccc25d5 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueBase.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueBase.java
@@ -60,9 +60,7 @@ public abstract class TestOverdueBase extends TestIntegrationBase {
         final String configXml = getOverdueConfig();
         final InputStream is = new ByteArrayInputStream(configXml.getBytes());
         final DefaultOverdueConfig config = XMLLoader.getObjectFromStreamNoValidation(is, DefaultOverdueConfig.class);
-        overdueWrapperFactory.setOverdueConfig(config);
-        overdueListener.setOverdueConfig(config);
-        ((DefaultOverdueInternalApi) overdueUserApi).setOverdueConfig(config);
+        overdueConfigCache.loadDefaultOverdueConfig(config);
 
         account = createAccountWithNonOsgiPaymentMethod(getAccountData(0));
         assertNotNull(account);
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 8eb8204..ee1d9be 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
@@ -72,6 +72,8 @@ import org.killbill.billing.lifecycle.glue.BusModule;
 import org.killbill.billing.mock.MockAccountBuilder;
 import org.killbill.billing.osgi.config.OSGIConfig;
 import org.killbill.billing.overdue.OverdueInternalApi;
+import org.killbill.billing.overdue.api.OverdueConfig;
+import org.killbill.billing.overdue.caching.OverdueConfigCache;
 import org.killbill.billing.overdue.listener.OverdueListener;
 import org.killbill.billing.overdue.wrapper.OverdueWrapperFactory;
 import org.killbill.billing.payment.api.Payment;
@@ -247,6 +249,9 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
     @Inject
     protected TestApiListener busHandler;
 
+    @Inject
+    protected OverdueConfigCache overdueConfigCache;
+
     protected void assertListenerStatus() {
         busHandler.assertListenerStatus();
     }
@@ -268,6 +273,8 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
 
         controlCacheDispatcher.clearAll();
 
+        overdueConfigCache.loadDefaultOverdueConfig((OverdueConfig) null);
+
         clock.resetDeltaFromReality();
         busHandler.reset();
 
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/api/DefaultOverdueInternalApi.java b/overdue/src/main/java/org/killbill/billing/overdue/api/DefaultOverdueInternalApi.java
index 63bc812..b5e0d13 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/api/DefaultOverdueInternalApi.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/api/DefaultOverdueInternalApi.java
@@ -15,18 +15,18 @@
  */
 
 package org.killbill.billing.overdue.api;
-import org.killbill.billing.overdue.OverdueInternalApi;
-import org.killbill.billing.overdue.config.DefaultOverdueConfig;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.entitlement.api.BlockingStateType;
 import org.killbill.billing.junction.BlockingInternalApi;
+import org.killbill.billing.overdue.OverdueInternalApi;
 import org.killbill.billing.overdue.OverdueService;
+import org.killbill.billing.overdue.caching.OverdueConfigCache;
+import org.killbill.billing.overdue.config.DefaultOverdueConfig;
 import org.killbill.billing.overdue.config.api.BillingState;
 import org.killbill.billing.overdue.config.api.OverdueException;
 import org.killbill.billing.overdue.config.api.OverdueStateSet;
@@ -35,6 +35,8 @@ import org.killbill.billing.overdue.wrapper.OverdueWrapperFactory;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.TenantContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.inject.Inject;
 
@@ -45,13 +47,16 @@ public class DefaultOverdueInternalApi implements OverdueInternalApi {
     private final OverdueWrapperFactory factory;
     private final BlockingInternalApi accessApi;
     private final InternalCallContextFactory internalCallContextFactory;
-
-    private DefaultOverdueConfig overdueConfig;
+    private final OverdueConfigCache overdueConfigCache;
 
     @Inject
-    public DefaultOverdueInternalApi(final OverdueWrapperFactory factory, final BlockingInternalApi accessApi, final InternalCallContextFactory internalCallContextFactory) {
+    public DefaultOverdueInternalApi(final OverdueWrapperFactory factory,
+                                     final BlockingInternalApi accessApi,
+                                     final OverdueConfigCache overdueConfigCache,
+                                     final InternalCallContextFactory internalCallContextFactory) {
         this.factory = factory;
         this.accessApi = accessApi;
+        this.overdueConfigCache = overdueConfigCache;
         this.internalCallContextFactory = internalCallContextFactory;
     }
 
@@ -59,8 +64,10 @@ public class DefaultOverdueInternalApi implements OverdueInternalApi {
     @Override
     public OverdueState getOverdueStateFor(final Account overdueable, final TenantContext context) throws OverdueException {
         try {
+            final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(context);
             final String stateName = accessApi.getBlockingStateForService(overdueable.getId(), BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, internalCallContextFactory.createInternalTenantContext(context)).getStateName();
-            final OverdueStateSet states = overdueConfig.getOverdueStatesAccount();
+            final OverdueConfig overdueConfig = overdueConfigCache.getOverdueConfig(internalTenantContext);
+            final OverdueStateSet states = ((DefaultOverdueConfig) overdueConfig).getOverdueStatesAccount();
             return states.findState(stateName);
         } catch (OverdueApiException e) {
             throw new OverdueException(e, ErrorCode.OVERDUE_CAT_ERROR_ENCOUNTERED, overdueable.getId(), overdueable.getClass().getSimpleName());
@@ -70,15 +77,18 @@ public class DefaultOverdueInternalApi implements OverdueInternalApi {
     @Override
     public BillingState getBillingStateFor(final Account overdueable, final TenantContext context) throws OverdueException {
         log.debug("Billing state of of {} requested", overdueable.getId());
-        final OverdueWrapper wrapper = factory.createOverdueWrapperFor(overdueable);
-        return wrapper.billingState(internalCallContextFactory.createInternalTenantContext(context));
+
+        final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(context);
+        final OverdueWrapper wrapper = factory.createOverdueWrapperFor(overdueable, internalTenantContext);
+        return wrapper.billingState(internalTenantContext);
     }
 
     @Override
     public OverdueState refreshOverdueStateFor(final Account blockable, final CallContext context) throws OverdueException, OverdueApiException {
         log.info("Refresh of blockable {} ({}) requested", blockable.getId(), blockable.getClass());
-        final OverdueWrapper wrapper = factory.createOverdueWrapperFor(blockable);
-        return wrapper.refresh(createInternalCallContext(blockable, context));
+        final InternalCallContext internalCallContext = createInternalCallContext(blockable, context);
+        final OverdueWrapper wrapper = factory.createOverdueWrapperFor(blockable, internalCallContext);
+        return wrapper.refresh(internalCallContext);
     }
 
     private InternalCallContext createInternalCallContext(final Account blockable, final CallContext context) {
@@ -89,8 +99,4 @@ public class DefaultOverdueInternalApi implements OverdueInternalApi {
     public void setOverrideBillingStateForAccount(final Account overdueable, final BillingState state, final CallContext context) {
         throw new UnsupportedOperationException();
     }
-
-    public void setOverdueConfig(final DefaultOverdueConfig config) {
-        this.overdueConfig = config;
-    }
 }
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/caching/EhCacheOverdueConfigCache.java b/overdue/src/main/java/org/killbill/billing/overdue/caching/EhCacheOverdueConfigCache.java
new file mode 100644
index 0000000..8a15187
--- /dev/null
+++ b/overdue/src/main/java/org/killbill/billing/overdue/caching/EhCacheOverdueConfigCache.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 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.overdue.caching;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.net.URI;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.ObjectType;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.overdue.api.OverdueApiException;
+import org.killbill.billing.overdue.api.OverdueConfig;
+import org.killbill.billing.overdue.config.DefaultOverdueConfig;
+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.TenantOverdueConfigCacheLoader.LoaderCallback;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.xmlloader.XMLLoader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class EhCacheOverdueConfigCache implements OverdueConfigCache {
+
+    private static final Logger log = LoggerFactory.getLogger(EhCacheOverdueConfigCache.class);
+
+    private OverdueConfig defaultOverdueConfig;
+
+    private final CacheController cacheController;
+    private final CacheLoaderArgument cacheLoaderArgument;
+
+    @Inject
+    public EhCacheOverdueConfigCache(final CacheControllerDispatcher cacheControllerDispatcher) {
+        this.cacheController = cacheControllerDispatcher.getCacheController(CacheType.TENANT_OVERDUE_CONFIG);
+        this.cacheLoaderArgument = initializeCacheLoaderArgument();
+    }
+
+    @Override
+    public void loadDefaultOverdueConfig(@Nullable final String configURI) throws OverdueApiException {
+        boolean missingOrCorruptedDefaultConfig;
+        try {
+            if (configURI == null || configURI.isEmpty()) {
+                missingOrCorruptedDefaultConfig = true;
+            } else {
+                final URI u = new URI(configURI);
+                defaultOverdueConfig = XMLLoader.getObjectFromUri(u, DefaultOverdueConfig.class);
+                missingOrCorruptedDefaultConfig = (defaultOverdueConfig == null);
+            }
+        } catch (Exception e) {
+            missingOrCorruptedDefaultConfig = true;
+            log.warn("Exception loading default overdue config from " + configURI, e);
+        }
+        if (missingOrCorruptedDefaultConfig) {
+            defaultOverdueConfig = new DefaultOverdueConfig();
+            log.warn("Overdue system disabled: unable to load the overdue config from " + configURI);
+        }
+    }
+
+    @Override
+    public void loadDefaultOverdueConfig(final OverdueConfig config) throws OverdueApiException {
+        defaultOverdueConfig = config;
+    }
+
+    @Override
+    public OverdueConfig getOverdueConfig(final InternalTenantContext tenantContext) throws OverdueApiException {
+
+        if (tenantContext.getTenantRecordId() == InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID) {
+            return defaultOverdueConfig;
+        }
+        // The cache loader might choke on some bad xml -- unlikely since we check its validity prior storing it,
+        // but to be on the safe side;;
+        try {
+            final OverdueConfig overdueConfig = (OverdueConfig) cacheController.get(tenantContext, cacheLoaderArgument);
+            return (overdueConfig != null) ? overdueConfig : defaultOverdueConfig;
+        } catch (IllegalStateException e) {
+            throw new OverdueApiException(ErrorCode.OVERDUE_INVALID_FOR_TENANT, tenantContext.getTenantRecordId());
+        }
+    }
+
+    private CacheLoaderArgument initializeCacheLoaderArgument() {
+        final LoaderCallback loaderCallback = new LoaderCallback() {
+            @Override
+            public Object loadCatalog(final String catalogXMLs) throws OverdueApiException {
+                final InputStream overdueConfigStream = new ByteArrayInputStream(catalogXMLs.getBytes());
+                final URI uri;
+                try {
+                    uri = new URI("/overdueConfig");
+                    final DefaultOverdueConfig overdueConfig = XMLLoader.getObjectFromStream(uri, overdueConfigStream, DefaultOverdueConfig.class);
+                    return overdueConfig;
+                } catch (Exception e) {
+                    throw new OverdueApiException(ErrorCode.OVERDUE_INVALID_FOR_TENANT, "Problem encountered loading overdue config ", e);
+                }
+            }
+        };
+        final Object[] args = new Object[1];
+        args[0] = loaderCallback;
+        final ObjectType irrelevant = null;
+        final InternalTenantContext notUsed = null;
+        return new CacheLoaderArgument(irrelevant, args, notUsed);
+    }
+
+}
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/caching/OverdueConfigCache.java b/overdue/src/main/java/org/killbill/billing/overdue/caching/OverdueConfigCache.java
new file mode 100644
index 0000000..0e1a65e
--- /dev/null
+++ b/overdue/src/main/java/org/killbill/billing/overdue/caching/OverdueConfigCache.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 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.overdue.caching;
+
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.overdue.api.OverdueApiException;
+import org.killbill.billing.overdue.api.OverdueConfig;
+
+public interface OverdueConfigCache {
+
+    public void loadDefaultOverdueConfig(String url) throws OverdueApiException;
+
+    public void loadDefaultOverdueConfig(OverdueConfig config) throws OverdueApiException;
+
+    public OverdueConfig getOverdueConfig(InternalTenantContext tenantContext) throws OverdueApiException;
+}
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/glue/DefaultOverdueModule.java b/overdue/src/main/java/org/killbill/billing/overdue/glue/DefaultOverdueModule.java
index 655674d..c79179c 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/glue/DefaultOverdueModule.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/glue/DefaultOverdueModule.java
@@ -28,6 +28,8 @@ import org.killbill.billing.overdue.api.OverdueApi;
 import org.killbill.billing.overdue.applicator.OverdueEmailGenerator;
 import org.killbill.billing.overdue.applicator.formatters.DefaultOverdueEmailFormatterFactory;
 import org.killbill.billing.overdue.applicator.formatters.OverdueEmailFormatterFactory;
+import org.killbill.billing.overdue.caching.EhCacheOverdueConfigCache;
+import org.killbill.billing.overdue.caching.OverdueConfigCache;
 import org.killbill.billing.overdue.listener.OverdueListener;
 import org.killbill.billing.overdue.notification.OverdueAsyncBusNotifier;
 import org.killbill.billing.overdue.notification.OverdueAsyncBusPoster;
@@ -56,6 +58,8 @@ public class DefaultOverdueModule extends KillBillModule implements OverdueModul
     protected void configure() {
         installOverdueUserApi();
 
+        installOverdueConfigCache();
+
         // internal bindings
         installOverdueService();
         installOverdueWrapperFactory();
@@ -91,4 +95,8 @@ public class DefaultOverdueModule extends KillBillModule implements OverdueModul
         bind(OverdueInternalApi.class).to(DefaultOverdueInternalApi.class).asEagerSingleton();
         bind(OverdueApi.class).to(DefaultOverdueApi.class).asEagerSingleton();
     }
+
+    public void installOverdueConfigCache() {
+        bind(OverdueConfigCache.class).to(EhCacheOverdueConfigCache.class).asEagerSingleton();
+    }
 }
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java b/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
index dccb0b6..a8d11b9 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
@@ -24,11 +24,16 @@ import javax.inject.Named;
 
 import org.killbill.billing.ObjectType;
 import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.events.ControlTagCreationInternalEvent;
 import org.killbill.billing.events.ControlTagDeletionInternalEvent;
 import org.killbill.billing.events.InvoiceAdjustmentInternalEvent;
 import org.killbill.billing.events.PaymentErrorInternalEvent;
 import org.killbill.billing.events.PaymentInfoInternalEvent;
+import org.killbill.billing.overdue.OverdueService;
+import org.killbill.billing.overdue.api.OverdueApiException;
+import org.killbill.billing.overdue.api.OverdueConfig;
+import org.killbill.billing.overdue.caching.OverdueConfigCache;
 import org.killbill.billing.overdue.config.DefaultOverdueConfig;
 import org.killbill.billing.overdue.config.DefaultOverdueState;
 import org.killbill.billing.overdue.glue.DefaultOverdueModule;
@@ -53,54 +58,56 @@ public class OverdueListener {
     private final InternalCallContextFactory internalCallContextFactory;
     private final OverduePoster asyncPoster;
     private final Clock clock;
-
-    private DefaultOverdueConfig config;
+    private final OverdueConfigCache overdueConfigCache;
 
     private static final Logger log = LoggerFactory.getLogger(OverdueListener.class);
 
     @Inject
     public OverdueListener(final Clock clock,
                            @Named(DefaultOverdueModule.OVERDUE_NOTIFIER_ASYNC_BUS_NAMED) final OverduePoster asyncPoster,
+                           final OverdueConfigCache overdueConfigCache,
                            final InternalCallContextFactory internalCallContextFactory) {
         this.asyncPoster = asyncPoster;
         this.clock = clock;
+        this.overdueConfigCache = overdueConfigCache;
         this.internalCallContextFactory = internalCallContextFactory;
     }
 
     @Subscribe
     public void handle_OVERDUE_ENFORCEMENT_OFF_Insert(final ControlTagCreationInternalEvent event) {
         if (event.getTagDefinition().getName().equals(ControlTagType.OVERDUE_ENFORCEMENT_OFF.toString()) && event.getObjectType() == ObjectType.ACCOUNT) {
-            insertBusEventIntoNotificationQueue(event.getObjectId(), event, OverdueAsyncBusNotificationAction.CLEAR);
+            insertBusEventIntoNotificationQueue(event.getObjectId(), event, OverdueAsyncBusNotificationAction.CLEAR, event.getSearchKey2());
         }
     }
 
     @Subscribe
     public void handle_OVERDUE_ENFORCEMENT_OFF_Removal(final ControlTagDeletionInternalEvent event) {
         if (event.getTagDefinition().getName().equals(ControlTagType.OVERDUE_ENFORCEMENT_OFF.toString()) && event.getObjectType() == ObjectType.ACCOUNT) {
-            insertBusEventIntoNotificationQueue(event.getObjectId(), event, OverdueAsyncBusNotificationAction.REFRESH);
+            insertBusEventIntoNotificationQueue(event.getObjectId(), event, OverdueAsyncBusNotificationAction.REFRESH, event.getSearchKey2());
         }
     }
 
     @Subscribe
     public void handlePaymentInfoEvent(final PaymentInfoInternalEvent event) {
         log.debug("Received PaymentInfo event {}", event);
-        insertBusEventIntoNotificationQueue(event.getAccountId(), event, OverdueAsyncBusNotificationAction.REFRESH);
+        insertBusEventIntoNotificationQueue(event.getAccountId(), event, OverdueAsyncBusNotificationAction.REFRESH, event.getSearchKey2());
     }
 
     @Subscribe
     public void handlePaymentErrorEvent(final PaymentErrorInternalEvent event) {
         log.debug("Received PaymentError event {}", event);
-        insertBusEventIntoNotificationQueue(event.getAccountId(), event, OverdueAsyncBusNotificationAction.REFRESH);
+        insertBusEventIntoNotificationQueue(event.getAccountId(), event, OverdueAsyncBusNotificationAction.REFRESH, event.getSearchKey2());
     }
 
     @Subscribe
     public void handleInvoiceAdjustmentEvent(final InvoiceAdjustmentInternalEvent event) {
         log.debug("Received InvoiceAdjustment event {}", event);
-        insertBusEventIntoNotificationQueue(event.getAccountId(), event, OverdueAsyncBusNotificationAction.REFRESH);
+        insertBusEventIntoNotificationQueue(event.getAccountId(), event, OverdueAsyncBusNotificationAction.REFRESH, event.getSearchKey2());
     }
 
-    private void insertBusEventIntoNotificationQueue(final UUID accountId, final BusEvent event, final OverdueAsyncBusNotificationAction action) {
-        final boolean shouldInsertNotification = shouldInsertNotification();
+    private void insertBusEventIntoNotificationQueue(final UUID accountId, final BusEvent event, final OverdueAsyncBusNotificationAction action, final Long tenantRecordId) {
+        final InternalTenantContext tenantContext = internalCallContextFactory.createInternalTenantContext(tenantRecordId, null);
+        final boolean shouldInsertNotification = shouldInsertNotification(tenantContext);
 
         if (shouldInsertNotification) {
             final OverdueAsyncBusNotificationKey notificationKey = new OverdueAsyncBusNotificationKey(accountId, action);
@@ -109,25 +116,27 @@ public class OverdueListener {
     }
 
     // Optimization: don't bother running the Overdue machinery if it's disabled
-    private boolean shouldInsertNotification() {
-        if (config == null || config.getOverdueStatesAccount() == null || config.getOverdueStatesAccount().getStates() == null) {
+    private boolean shouldInsertNotification(final InternalTenantContext internalTenantContext) {
+        OverdueConfig overdueConfig;
+        try {
+            overdueConfig = overdueConfigCache.getOverdueConfig(internalTenantContext);
+        } catch (OverdueApiException e) {
+            log.warn("Failed to extract overdue config for tenant " + internalTenantContext.getTenantRecordId());
+            overdueConfig = null;
+        }
+        if (overdueConfig == null || overdueConfig.getOverdueStatesAccount() == null || overdueConfig.getOverdueStatesAccount().getStates() == null) {
             return false;
         }
 
-        for (final DefaultOverdueState state : config.getOverdueStatesAccount().getStates()) {
+        for (final DefaultOverdueState state : ((DefaultOverdueConfig) overdueConfig).getOverdueStatesAccount().getStates()) {
             if (state.getConditionEvaluation() != null) {
                 return true;
             }
         }
-
         return false;
     }
 
     private InternalCallContext createCallContext(final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
         return internalCallContextFactory.createInternalCallContext(tenantRecordId, accountRecordId, "OverdueService", CallOrigin.INTERNAL, UserType.SYSTEM, userToken);
     }
-
-    public void setOverdueConfig(final DefaultOverdueConfig config) {
-        this.config = config;
-    }
 }
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/service/DefaultOverdueService.java b/overdue/src/main/java/org/killbill/billing/overdue/service/DefaultOverdueService.java
index ef5cfcf..d7f37b7 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/service/DefaultOverdueService.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/service/DefaultOverdueService.java
@@ -23,18 +23,22 @@ import java.net.URISyntaxException;
 
 import javax.inject.Named;
 
+import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.lifecycle.api.BusService;
-import org.killbill.billing.overdue.OverdueInternalApi;
 import org.killbill.billing.overdue.OverdueProperties;
 import org.killbill.billing.overdue.OverdueService;
-import org.killbill.billing.overdue.api.DefaultOverdueInternalApi;
+import org.killbill.billing.overdue.api.OverdueApiException;
+import org.killbill.billing.overdue.api.OverdueConfig;
+import org.killbill.billing.overdue.caching.EhCacheOverdueConfigCache;
+import org.killbill.billing.overdue.caching.OverdueConfigCache;
 import org.killbill.billing.overdue.config.DefaultOverdueConfig;
 import org.killbill.billing.overdue.glue.DefaultOverdueModule;
 import org.killbill.billing.overdue.listener.OverdueListener;
 import org.killbill.billing.overdue.notification.OverdueNotifier;
-import org.killbill.billing.overdue.wrapper.OverdueWrapperFactory;
 import org.killbill.billing.platform.api.LifecycleHandlerType;
 import org.killbill.billing.platform.api.LifecycleHandlerType.LifecycleLevel;
+import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.bus.api.PersistentBus.EventBusException;
 import org.killbill.xmlloader.XMLLoader;
 import org.slf4j.Logger;
@@ -48,34 +52,31 @@ public class DefaultOverdueService implements OverdueService {
 
     public static final String OVERDUE_SERVICE_NAME = "overdue-service";
 
-    private final OverdueInternalApi userApi;
     private final OverdueProperties properties;
     private final OverdueNotifier asyncNotifier;
     private final OverdueNotifier checkNotifier;
     private final BusService busService;
     private final OverdueListener listener;
-    private final OverdueWrapperFactory factory;
+
+    private final OverdueConfigCache overdueConfigCache;
 
     private DefaultOverdueConfig overdueConfig;
     private boolean isConfigLoaded;
 
     @Inject
-    public DefaultOverdueService(
-            final OverdueInternalApi userApi,
-            final OverdueProperties properties,
-            @Named(DefaultOverdueModule.OVERDUE_NOTIFIER_CHECK_NAMED) final OverdueNotifier checkNotifier,
-            @Named(DefaultOverdueModule.OVERDUE_NOTIFIER_ASYNC_BUS_NAMED) final OverdueNotifier asyncNotifier,
-            final BusService busService,
-            final OverdueListener listener,
-            final OverdueWrapperFactory factory) {
-        this.userApi = userApi;
+    public DefaultOverdueService(final OverdueProperties properties,
+                                 @Named(DefaultOverdueModule.OVERDUE_NOTIFIER_CHECK_NAMED) final OverdueNotifier checkNotifier,
+                                 @Named(DefaultOverdueModule.OVERDUE_NOTIFIER_ASYNC_BUS_NAMED) final OverdueNotifier asyncNotifier,
+                                 final BusService busService,
+                                 final OverdueListener listener,
+                                 final OverdueConfigCache overdueConfigCache) {
         this.properties = properties;
         this.checkNotifier = checkNotifier;
         this.asyncNotifier = asyncNotifier;
         this.busService = busService;
         this.listener = listener;
-        this.factory = factory;
         this.isConfigLoaded = false;
+        this.overdueConfigCache = overdueConfigCache;
     }
 
     @Override
@@ -83,38 +84,16 @@ public class DefaultOverdueService implements OverdueService {
         return OVERDUE_SERVICE_NAME;
     }
 
-    @Override
-    public OverdueInternalApi getUserApi() {
-        return userApi;
-    }
-
     @LifecycleHandlerType(LifecycleLevel.LOAD_CATALOG)
     public synchronized void loadConfig() throws ServiceException {
         if (!isConfigLoaded) {
             try {
-                final URI u = new URI(properties.getConfigURI());
-                overdueConfig = XMLLoader.getObjectFromUri(u, DefaultOverdueConfig.class);
-                // File not found?
-                if (overdueConfig == null) {
-                    log.warn("Overdue system disabled: unable to load the overdue config from " + properties.getConfigURI());
-                    overdueConfig = new DefaultOverdueConfig();
-                }
-
+                overdueConfigCache.loadDefaultOverdueConfig(properties.getConfigURI());
                 isConfigLoaded = true;
-            } catch (final URISyntaxException e) {
+            } catch (OverdueApiException e) {
                 log.warn("Overdue system disabled: unable to load the overdue config from " + properties.getConfigURI(), e);
-                overdueConfig = new DefaultOverdueConfig();
-            } catch (final IllegalArgumentException e) {
-                log.warn("Overdue system disabled: unable to load the overdue config from " + properties.getConfigURI(), e);
-                overdueConfig = new DefaultOverdueConfig();
-            } catch (final Exception e) {
-                log.warn("Unable to load the overdue config from " + properties.getConfigURI(), e);
-                throw new ServiceException(e);
+                e.printStackTrace();
             }
-
-            factory.setOverdueConfig(overdueConfig);
-            listener.setOverdueConfig(overdueConfig);
-            ((DefaultOverdueInternalApi) userApi).setOverdueConfig(overdueConfig);
         }
     }
 
@@ -149,4 +128,9 @@ public class DefaultOverdueService implements OverdueService {
         checkNotifier.stop();
         asyncNotifier.stop();
     }
+
+    @Override
+    public OverdueConfig getOverdueConfig(final InternalTenantContext internalTenantContext) throws OverdueApiException {
+        return overdueConfigCache.getOverdueConfig(internalTenantContext);
+    }
 }
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapperFactory.java b/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapperFactory.java
index c234726..c224c83 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapperFactory.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapperFactory.java
@@ -19,22 +19,25 @@ package org.killbill.billing.overdue.wrapper;
 import java.util.UUID;
 
 import org.joda.time.Period;
-import org.killbill.billing.overdue.config.DefaultOverdueConfig;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountApiException;
-import org.killbill.clock.Clock;
+import org.killbill.billing.account.api.AccountInternalApi;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.junction.BlockingInternalApi;
+import org.killbill.billing.overdue.OverdueService;
+import org.killbill.billing.overdue.api.OverdueApiException;
+import org.killbill.billing.overdue.api.OverdueConfig;
 import org.killbill.billing.overdue.applicator.OverdueStateApplicator;
+import org.killbill.billing.overdue.caching.OverdueConfigCache;
 import org.killbill.billing.overdue.calculator.BillingStateCalculator;
+import org.killbill.billing.overdue.config.DefaultOverdueConfig;
 import org.killbill.billing.overdue.config.DefaultOverdueState;
 import org.killbill.billing.overdue.config.DefaultOverdueStateSet;
 import org.killbill.billing.overdue.config.api.OverdueException;
 import org.killbill.billing.overdue.config.api.OverdueStateSet;
-import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.billing.account.api.AccountInternalApi;
-import org.killbill.billing.junction.BlockingInternalApi;
+import org.killbill.clock.Clock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.inject.Inject;
 
@@ -47,23 +50,24 @@ public class OverdueWrapperFactory {
     private final OverdueStateApplicator overdueStateApplicator;
     private final BlockingInternalApi api;
     private final Clock clock;
-    private DefaultOverdueConfig config;
+    private final OverdueConfigCache overdueConfigCache;
 
     @Inject
     public OverdueWrapperFactory(final BlockingInternalApi api, final Clock clock,
                                  final BillingStateCalculator billingStateCalculator,
                                  final OverdueStateApplicator overdueStateApplicatorBundle,
+                                 final OverdueConfigCache overdueConfigCache,
                                  final AccountInternalApi accountApi) {
         this.billingStateCalculator = billingStateCalculator;
         this.overdueStateApplicator = overdueStateApplicatorBundle;
         this.accountApi = accountApi;
         this.api = api;
         this.clock = clock;
+        this.overdueConfigCache = overdueConfigCache;
     }
-
     @SuppressWarnings("unchecked")
-    public OverdueWrapper createOverdueWrapperFor(final Account blockable) throws OverdueException {
-        return (OverdueWrapper) new OverdueWrapper(blockable, api, getOverdueStateSet(),
+    public OverdueWrapper createOverdueWrapperFor(final Account blockable, final InternalTenantContext context) throws OverdueException {
+        return (OverdueWrapper) new OverdueWrapper(blockable, api, getOverdueStateSet(context),
                                                    clock, billingStateCalculator, overdueStateApplicator);
     }
 
@@ -71,37 +75,39 @@ public class OverdueWrapperFactory {
     public OverdueWrapper createOverdueWrapperFor(final UUID id, final InternalTenantContext context) throws OverdueException {
 
         try {
-            Account account = accountApi.getAccountById(id, context);
-            return new OverdueWrapper(account, api, getOverdueStateSet(),
+            final Account account = accountApi.getAccountById(id, context);
+            return new OverdueWrapper(account, api, getOverdueStateSet(context),
                                       clock, billingStateCalculator, overdueStateApplicator);
-
         } catch (AccountApiException e) {
             throw new OverdueException(e);
         }
     }
 
-    private OverdueStateSet getOverdueStateSet() {
-        if (config == null || config.getOverdueStatesAccount() == null) {
-            return new DefaultOverdueStateSet() {
-
-                @SuppressWarnings("unchecked")
-                @Override
-                public DefaultOverdueState[] getStates() {
-                    return new DefaultOverdueState[0];
-                }
 
-                @Override
-                public Period getInitialReevaluationInterval() {
-                    return null;
-                }
-            };
-        } else {
-            return config.getOverdueStatesAccount();
+    private OverdueStateSet getOverdueStateSet(final InternalTenantContext context) throws OverdueException {
+        final OverdueConfig overdueConfig;
+        try {
+            overdueConfig = overdueConfigCache.getOverdueConfig(context);
+            if (overdueConfig == null || overdueConfig.getOverdueStatesAccount() == null) {
+                return new DefaultOverdueStateSet() {
+
+                    @SuppressWarnings("unchecked")
+                    @Override
+                    public DefaultOverdueState[] getStates() {
+                        return new DefaultOverdueState[0];
+                    }
+
+                    @Override
+                    public Period getInitialReevaluationInterval() {
+                        return null;
+                    }
+                };
+            } else {
+                return ((DefaultOverdueConfig) overdueConfig).getOverdueStatesAccount();
+            }
+        } catch (OverdueApiException e) {
+            throw new OverdueException(e);
         }
     }
-
-    public void setOverdueConfig(final DefaultOverdueConfig config) {
-        this.config = config;
-    }
-
 }
+
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/applicator/TestOverdueStateApplicator.java b/overdue/src/test/java/org/killbill/billing/overdue/applicator/TestOverdueStateApplicator.java
index 457696e..ac23e0a 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/applicator/TestOverdueStateApplicator.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/applicator/TestOverdueStateApplicator.java
@@ -44,7 +44,6 @@ public class TestOverdueStateApplicator extends OverdueTestSuiteWithEmbeddedDB {
     public void testApplicator() throws Exception {
         final InputStream is = new ByteArrayInputStream(testOverdueHelper.getConfigXml().getBytes());
         final DefaultOverdueConfig config = XMLLoader.getObjectFromStreamNoValidation(is, DefaultOverdueConfig.class);
-        overdueWrapperFactory.setOverdueConfig(config);
 
         final Account account = Mockito.mock(Account.class);
         Mockito.when(account.getId()).thenReturn(UUID.randomUUID());
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/caching/MockOverdueConfigCache.java b/overdue/src/test/java/org/killbill/billing/overdue/caching/MockOverdueConfigCache.java
new file mode 100644
index 0000000..7d88488
--- /dev/null
+++ b/overdue/src/test/java/org/killbill/billing/overdue/caching/MockOverdueConfigCache.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 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.overdue.caching;
+
+import javax.inject.Inject;
+
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.overdue.api.OverdueApiException;
+import org.killbill.billing.overdue.api.OverdueConfig;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
+
+public class MockOverdueConfigCache extends EhCacheOverdueConfigCache implements OverdueConfigCache
+{
+
+    private OverdueConfig overwriteDefaultOverdueConfig;
+
+    @Inject
+    public MockOverdueConfigCache(final CacheControllerDispatcher cacheControllerDispatcher) {
+        super(cacheControllerDispatcher);
+    }
+
+    @Override
+    public void loadDefaultOverdueConfig(final String url) throws OverdueApiException {
+        super.loadDefaultOverdueConfig(url);
+    }
+
+    public void loadOverwriteDefaultOverdueConfig(final OverdueConfig overdueConfig) throws OverdueApiException {
+        this.overwriteDefaultOverdueConfig = overdueConfig;
+    }
+
+    public void clearOverwriteDefaultOverdueConfig() {
+        this.overwriteDefaultOverdueConfig = null;
+    }
+
+    @Override
+    public OverdueConfig getOverdueConfig(final InternalTenantContext tenantContext) throws OverdueApiException {
+        if (overwriteDefaultOverdueConfig != null) {
+            return overwriteDefaultOverdueConfig;
+        }
+        return super.getOverdueConfig(tenantContext);
+    }
+}
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModule.java b/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModule.java
index bac5878..0284e6d 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModule.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModule.java
@@ -25,6 +25,9 @@ import org.killbill.billing.mock.glue.MockTagModule;
 import org.killbill.billing.mock.glue.MockTenantModule;
 import org.killbill.billing.overdue.TestOverdueHelper;
 import org.killbill.billing.overdue.applicator.OverdueBusListenerTester;
+import org.killbill.billing.overdue.caching.EhCacheOverdueConfigCache;
+import org.killbill.billing.overdue.caching.MockOverdueConfigCache;
+import org.killbill.billing.overdue.caching.OverdueConfigCache;
 import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.email.EmailModule;
 import org.killbill.billing.util.email.templates.TemplateModule;
@@ -62,4 +65,9 @@ public class TestOverdueModule extends DefaultOverdueModule {
         bind(OverdueBusListenerTester.class).asEagerSingleton();
         bind(TestOverdueHelper.class).asEagerSingleton();
     }
+
+    public void installOverdueConfigCache() {
+        bind(OverdueConfigCache.class).to(MockOverdueConfigCache.class).asEagerSingleton();
+    }
+
 }
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/OverdueTestSuiteWithEmbeddedDB.java b/overdue/src/test/java/org/killbill/billing/overdue/OverdueTestSuiteWithEmbeddedDB.java
index b89b3c0..1253410 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/OverdueTestSuiteWithEmbeddedDB.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/OverdueTestSuiteWithEmbeddedDB.java
@@ -27,6 +27,7 @@ import org.killbill.billing.junction.BlockingInternalApi;
 import org.killbill.billing.lifecycle.api.BusService;
 import org.killbill.billing.overdue.applicator.OverdueBusListenerTester;
 import org.killbill.billing.overdue.applicator.OverdueStateApplicator;
+import org.killbill.billing.overdue.caching.OverdueConfigCache;
 import org.killbill.billing.overdue.calculator.BillingStateCalculator;
 import org.killbill.billing.overdue.glue.DefaultOverdueModule;
 import org.killbill.billing.overdue.glue.TestOverdueModuleWithEmbeddedDB;
@@ -95,6 +96,8 @@ public abstract class OverdueTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu
     protected NonEntityDao nonEntityDao;
     @Inject
     protected TestOverdueHelper testOverdueHelper;
+    @Inject
+    protected OverdueConfigCache overdueConfigCache;
 
     @BeforeClass(groups = "slow")
     protected void beforeClass() throws Exception {
@@ -108,6 +111,7 @@ public abstract class OverdueTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu
         cacheControllerDispatcher.clearAll();
         bus.start();
         bus.register(listener);
+        service.loadConfig();
         service.initialize();
         service.start();
     }
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/wrapper/TestOverdueWrapper.java b/overdue/src/test/java/org/killbill/billing/overdue/wrapper/TestOverdueWrapper.java
index 644e19f..927af22 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/wrapper/TestOverdueWrapper.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/wrapper/TestOverdueWrapper.java
@@ -19,23 +19,30 @@ package org.killbill.billing.overdue.wrapper;
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.junction.DefaultBlockingState;
+import org.killbill.billing.overdue.OverdueTestSuiteWithEmbeddedDB;
 import org.killbill.billing.overdue.api.OverdueState;
+import org.killbill.billing.overdue.caching.MockOverdueConfigCache;
 import org.killbill.billing.overdue.config.DefaultOverdueConfig;
+import org.killbill.xmlloader.XMLLoader;
 import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
-import org.killbill.billing.account.api.Account;
-import org.killbill.billing.overdue.OverdueTestSuiteWithEmbeddedDB;
-import org.killbill.xmlloader.XMLLoader;
-import org.killbill.billing.junction.DefaultBlockingState;
-
 public class TestOverdueWrapper extends OverdueTestSuiteWithEmbeddedDB {
 
+    @BeforeMethod(groups = "slow")
+    public void beforeMethod() throws Exception {
+        super.beforeMethod();
+        ((MockOverdueConfigCache) overdueConfigCache).loadOverwriteDefaultOverdueConfig(null);
+    }
+
     @Test(groups = "slow")
     public void testWrapperBasic() throws Exception {
         final InputStream is = new ByteArrayInputStream(testOverdueHelper.getConfigXml().getBytes());
         final DefaultOverdueConfig config = XMLLoader.getObjectFromStreamNoValidation(is, DefaultOverdueConfig.class);
-        overdueWrapperFactory.setOverdueConfig(config);
+        ((MockOverdueConfigCache) overdueConfigCache).loadOverwriteDefaultOverdueConfig(config);
 
         Account account;
         OverdueWrapper wrapper;
@@ -43,26 +50,25 @@ public class TestOverdueWrapper extends OverdueTestSuiteWithEmbeddedDB {
 
         state = config.getOverdueStatesAccount().findState("OD1");
         account = testOverdueHelper.createAccount(clock.getUTCToday().minusDays(31));
-        wrapper = overdueWrapperFactory.createOverdueWrapperFor(account);
+        wrapper = overdueWrapperFactory.createOverdueWrapperFor(account, internalCallContext);
         wrapper.refresh(internalCallContext);
         testOverdueHelper.checkStateApplied(state);
 
         state = config.getOverdueStatesAccount().findState("OD2");
         account = testOverdueHelper.createAccount(clock.getUTCToday().minusDays(41));
-        wrapper = overdueWrapperFactory.createOverdueWrapperFor(account);
+        wrapper = overdueWrapperFactory.createOverdueWrapperFor(account, internalCallContext);
         wrapper.refresh(internalCallContext);
         testOverdueHelper.checkStateApplied(state);
 
         state = config.getOverdueStatesAccount().findState("OD3");
         account = testOverdueHelper.createAccount(clock.getUTCToday().minusDays(51));
-        wrapper = overdueWrapperFactory.createOverdueWrapperFor(account);
+        wrapper = overdueWrapperFactory.createOverdueWrapperFor(account, internalCallContext);
         wrapper.refresh(internalCallContext);
         testOverdueHelper.checkStateApplied(state);
     }
 
     @Test(groups = "slow")
     public void testWrapperNoConfig() throws Exception {
-        overdueWrapperFactory.setOverdueConfig(null);
 
         final Account account;
         final OverdueWrapper wrapper;
@@ -72,7 +78,7 @@ public class TestOverdueWrapper extends OverdueTestSuiteWithEmbeddedDB {
         final DefaultOverdueConfig config = XMLLoader.getObjectFromStreamNoValidation(is, DefaultOverdueConfig.class);
         state = config.getOverdueStatesAccount().findState(DefaultBlockingState.CLEAR_STATE_NAME);
         account = testOverdueHelper.createAccount(clock.getUTCToday().minusDays(31));
-        wrapper = overdueWrapperFactory.createOverdueWrapperFor(account);
+        wrapper = overdueWrapperFactory.createOverdueWrapperFor(account, internalCallContext);
         final OverdueState result = wrapper.refresh(internalCallContext);
 
         Assert.assertEquals(result.getName(), state.getName());
diff --git a/tenant/src/main/java/org/killbill/billing/tenant/api/DefaultTenantInternalApi.java b/tenant/src/main/java/org/killbill/billing/tenant/api/DefaultTenantInternalApi.java
index 4d27d09..fa9454b 100644
--- a/tenant/src/main/java/org/killbill/billing/tenant/api/DefaultTenantInternalApi.java
+++ b/tenant/src/main/java/org/killbill/billing/tenant/api/DefaultTenantInternalApi.java
@@ -40,4 +40,10 @@ public class DefaultTenantInternalApi implements TenantInternalApi {
     public List<String> getTenantCatalogs(final InternalTenantContext tenantContext) {
         return tenantDao.getTenantValueForKey(TenantKey.CATALOG.toString(), tenantContext);
     }
+
+    @Override
+    public String getTenantOverdueConfig(final InternalTenantContext tenantContext) {
+        final List<String> values = tenantDao.getTenantValueForKey(TenantKey.OVERDUE_CONFIG.toString(), tenantContext);
+        return values.isEmpty() ? null : values.get(0);
+    }
 }
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 a3f3fd1..024bdff 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
@@ -32,6 +32,7 @@ public @interface Cachable {
     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 CacheType value();
 
@@ -55,7 +56,10 @@ public @interface Cachable {
         AUDIT_LOG_VIA_HISTORY(AUDIT_LOG_VIA_HISTORY_CACHE_NAME, true),
 
         /* Tenant catalog cache */
-        TENANT_CATALOG(TENANT_CATALOG_CACHE_NAME, false);
+        TENANT_CATALOG(TENANT_CATALOG_CACHE_NAME, false),
+
+        /* Tenant overdue config cache */
+        TENANT_OVERDUE_CONFIG(TENANT_OVERDUE_CONFIG_CACHE_NAME, false);
 
         private final String cacheName;
         private final boolean isKeyPrefixedWithTableName;
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 adea4d8..ab5eaa5 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
@@ -46,7 +46,8 @@ public class EhCacheCacheManagerProvider implements Provider<CacheManager> {
                                        final ObjectIdCacheLoader objectIdCacheLoader,
                                        final AuditLogCacheLoader auditLogCacheLoader,
                                        final AuditLogViaHistoryCacheLoader auditLogViaHistoryCacheLoader,
-                                       final TenantCatalogCacheLoader tenantCatalogCacheLoader) {
+                                       final TenantCatalogCacheLoader tenantCatalogCacheLoader,
+                                       final TenantOverdueConfigCacheLoader tenantOverdueConfigCacheLoader) {
         this.cacheConfig = cacheConfig;
         cacheLoaders.add(recordIdCacheLoader);
         cacheLoaders.add(accountRecordIdCacheLoader);
@@ -55,6 +56,7 @@ public class EhCacheCacheManagerProvider implements Provider<CacheManager> {
         cacheLoaders.add(auditLogCacheLoader);
         cacheLoaders.add(auditLogViaHistoryCacheLoader);
         cacheLoaders.add(tenantCatalogCacheLoader);
+        cacheLoaders.add(tenantOverdueConfigCacheLoader);
     }
 
     @Override
@@ -78,10 +80,8 @@ public class EhCacheCacheManagerProvider implements Provider<CacheManager> {
             for (final CacheLoader existingCacheLoader : cache.getRegisteredCacheLoaders()) {
                 cache.unregisterCacheLoader(existingCacheLoader);
             }
-
             cache.registerCacheLoader(cacheLoader);
         }
-
         return cacheManager;
     }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/cache/TenantOverdueConfigCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/TenantOverdueConfigCacheLoader.java
new file mode 100644
index 0000000..3665bc0
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/cache/TenantOverdueConfigCacheLoader.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 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 javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.overdue.api.OverdueApiException;
+import org.killbill.billing.tenant.api.TenantInternalApi;
+import org.killbill.billing.util.cache.Cachable.CacheType;
+
+@Singleton
+public class TenantOverdueConfigCacheLoader extends BaseCacheLoader {
+
+    private final TenantInternalApi tenantApi;
+
+    @Inject
+    public TenantOverdueConfigCacheLoader(final TenantInternalApi tenantApi) {
+        super();
+        this.tenantApi = tenantApi;
+    }
+
+    @Override
+    public CacheType getCacheType() {
+        return CacheType.TENANT_OVERDUE_CONFIG;
+    }
+
+    @Override
+    public Object load(final Object key, final Object argument) {
+        checkCacheLoaderStatus();
+
+        if (!(key instanceof InternalTenantContext)) {
+            throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
+        }
+        if (!(argument instanceof CacheLoaderArgument)) {
+            throw new IllegalArgumentException("Unexpected argument type of " + argument.getClass().getName());
+        }
+
+        final InternalTenantContext internalTenantContext = (InternalTenantContext) key;
+        final 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];
+
+        final String overdueXML = tenantApi.getTenantOverdueConfig(internalTenantContext);
+        if (overdueXML == null) {
+            return null;
+        }
+        try {
+            return callback.loadCatalog(overdueXML);
+        } catch (OverdueApiException e) {
+            throw new IllegalStateException(String.format("Failed to de-serialize overdue config for tenant %s : %s",
+                                                          internalTenantContext.getTenantRecordId(), e.getMessage()), e);
+        }
+    }
+
+    public interface LoaderCallback {
+
+        public Object loadCatalog(final String overdueXML) throws OverdueApiException;
+    }
+}
diff --git a/util/src/main/resources/ehcache.xml b/util/src/main/resources/ehcache.xml
index a97dbe9..7159b93 100644
--- a/util/src/main/resources/ehcache.xml
+++ b/util/src/main/resources/ehcache.xml
@@ -129,5 +129,18 @@
                 properties=""/>
     </cache>
 
+    <cache name="tenant-overdue-config"
+           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/mock/glue/MockOverdueModule.java b/util/src/test/java/org/killbill/billing/mock/glue/MockOverdueModule.java
index 3749060..0a88273 100644
--- a/util/src/test/java/org/killbill/billing/mock/glue/MockOverdueModule.java
+++ b/util/src/test/java/org/killbill/billing/mock/glue/MockOverdueModule.java
@@ -21,12 +21,16 @@ package org.killbill.billing.mock.glue;
 import org.killbill.billing.glue.OverdueModule;
 import org.killbill.billing.overdue.OverdueInternalApi;
 import org.killbill.billing.overdue.api.OverdueApi;
+import org.killbill.billing.overdue.api.OverdueConfig;
 import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.glue.KillBillModule;
 import org.mockito.Mockito;
 
+import com.google.inject.Inject;
+
 public class MockOverdueModule extends KillBillModule implements OverdueModule {
 
+    @Inject
     public MockOverdueModule(final KillbillConfigSource configSource) {
         super(configSource);
     }
@@ -37,6 +41,7 @@ public class MockOverdueModule extends KillBillModule implements OverdueModule {
         bind(OverdueApi.class).toInstance(Mockito.mock(OverdueApi.class));
     }
 
+
     @Override
     protected void configure() {
         installOverdueUserApi();