killbill-uncached

Details

diff --git a/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheOverriddenPlanCache.java b/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheOverriddenPlanCache.java
new file mode 100644
index 0000000..5e94a51
--- /dev/null
+++ b/catalog/src/main/java/org/killbill/billing/catalog/caching/EhCacheOverriddenPlanCache.java
@@ -0,0 +1,113 @@
+/*
+ * 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.catalog.caching;
+
+import java.util.List;
+import java.util.regex.Matcher;
+
+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.DefaultPlan;
+import org.killbill.billing.catalog.DefaultPlanPhasePriceOverride;
+import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.catalog.api.Plan;
+import org.killbill.billing.catalog.api.PlanPhase;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
+import org.killbill.billing.catalog.api.StaticCatalog;
+import org.killbill.billing.catalog.dao.CatalogOverrideDao;
+import org.killbill.billing.catalog.dao.CatalogOverridePhaseDefinitionModelDao;
+import org.killbill.billing.catalog.override.DefaultPriceOverride;
+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.OverriddenPlanCacheLoader.LoaderCallback;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+
+public class EhCacheOverriddenPlanCache implements OverriddenPlanCache {
+
+    private final CacheController cacheController;
+    private final LoaderCallback loaderCallback;
+    private final CatalogOverrideDao overrideDao;
+
+    @Inject
+    public EhCacheOverriddenPlanCache(final CatalogOverrideDao overrideDao, final CacheControllerDispatcher cacheControllerDispatcher) {
+        this.overrideDao = overrideDao;
+        this.cacheController = cacheControllerDispatcher.getCacheController(CacheType.OVERRIDDEN_PLAN);
+        this.loaderCallback = new LoaderCallback() {
+            @Override
+            public Object loadPlan(final String planName, final StaticCatalog catalog, final InternalTenantContext context) throws CatalogApiException {
+                return loadOverriddenPlan(planName, catalog, context);
+            }
+        };
+    }
+
+    @Override
+    public DefaultPlan getOverriddenPlan(final String planName, final StaticCatalog catalog, final InternalTenantContext context) {
+
+        final ObjectType irrelevant = null;
+        final Object[] args = new Object[2];
+        args[0] = loaderCallback;
+        args[1] = catalog;
+
+        final CacheLoaderArgument argument = new CacheLoaderArgument(irrelevant, args, context);
+        return (DefaultPlan) cacheController.get(planName, argument);
+    }
+
+    private DefaultPlan loadOverriddenPlan(final String planName, final StaticCatalog catalog, final InternalTenantContext context) throws CatalogApiException {
+
+        final Matcher m = DefaultPriceOverride.CUSTOM_PLAN_NAME_PATTERN.matcher(planName);
+        if (!m.matches()) {
+            throw new CatalogApiException(ErrorCode.CAT_NO_SUCH_PLAN, planName);
+        }
+        final String parentPlanName = m.group(1);
+        final Long planDefRecordId = Long.parseLong(m.group(2));
+
+        final List<CatalogOverridePhaseDefinitionModelDao> phaseDefs = overrideDao.getOverriddenPlanPhases(planDefRecordId, context);
+        final DefaultPlan defaultPlan = (DefaultPlan) catalog.findCurrentPlan(parentPlanName);
+
+        final PlanPhasePriceOverride[] overrides = createOverrides(defaultPlan, phaseDefs);
+        return new DefaultPlan(planName, defaultPlan, overrides);
+    }
+
+    private PlanPhasePriceOverride[] createOverrides(final Plan defaultPlan, final List<CatalogOverridePhaseDefinitionModelDao> phaseDefs) {
+
+        final PlanPhasePriceOverride[] result = new PlanPhasePriceOverride[defaultPlan.getAllPhases().length];
+
+        for (int i = 0; i < defaultPlan.getAllPhases().length; i++) {
+
+            final PlanPhase curPhase = defaultPlan.getAllPhases()[i];
+            final CatalogOverridePhaseDefinitionModelDao overriddenPhase = Iterables.tryFind(phaseDefs, new Predicate<CatalogOverridePhaseDefinitionModelDao>() {
+                @Override
+                public boolean apply(final CatalogOverridePhaseDefinitionModelDao input) {
+                    return input.getParentPhaseName().equals(curPhase.getName());
+                }
+            }).orNull();
+            result[i] = (overriddenPhase != null) ?
+                        new DefaultPlanPhasePriceOverride(curPhase.getName(), Currency.valueOf(overriddenPhase.getCurrency()), overriddenPhase.getFixedPrice(), overriddenPhase.getRecurringPrice()) :
+                        null;
+        }
+        return result;
+    }
+}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/caching/OverriddenPlanCache.java b/catalog/src/main/java/org/killbill/billing/catalog/caching/OverriddenPlanCache.java
new file mode 100644
index 0000000..f0f8178
--- /dev/null
+++ b/catalog/src/main/java/org/killbill/billing/catalog/caching/OverriddenPlanCache.java
@@ -0,0 +1,28 @@
+/*
+ * 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.catalog.caching;
+
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.DefaultPlan;
+import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.StaticCatalog;
+
+public interface OverriddenPlanCache {
+
+    DefaultPlan getOverriddenPlan(final String planName, final StaticCatalog catalog, final InternalTenantContext context) throws CatalogApiException;
+}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/glue/CatalogModule.java b/catalog/src/main/java/org/killbill/billing/catalog/glue/CatalogModule.java
index 32685e9..4e93f86 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/glue/CatalogModule.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/glue/CatalogModule.java
@@ -25,6 +25,8 @@ import org.killbill.billing.catalog.api.user.DefaultCatalogUserApi;
 import org.killbill.billing.catalog.caching.CatalogCache;
 import org.killbill.billing.catalog.caching.CatalogCacheInvalidationCallback;
 import org.killbill.billing.catalog.caching.EhCacheCatalogCache;
+import org.killbill.billing.catalog.caching.EhCacheOverriddenPlanCache;
+import org.killbill.billing.catalog.caching.OverriddenPlanCache;
 import org.killbill.billing.catalog.dao.CatalogOverrideDao;
 import org.killbill.billing.catalog.dao.DefaultCatalogOverrideDao;
 import org.killbill.billing.catalog.io.CatalogLoader;
@@ -69,6 +71,8 @@ public class CatalogModule extends KillBillModule {
     public void installCatalogConfigCache() {
         bind(CatalogCache.class).to(EhCacheCatalogCache.class).asEagerSingleton();
         bind(CacheInvalidationCallback.class).annotatedWith(Names.named(CATALOG_INVALIDATION_CALLBACK)).to(CatalogCacheInvalidationCallback.class).asEagerSingleton();
+
+        bind(OverriddenPlanCache.class).to(EhCacheOverriddenPlanCache.class).asEagerSingleton();
     }
 
 
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/override/DefaultPriceOverride.java b/catalog/src/main/java/org/killbill/billing/catalog/override/DefaultPriceOverride.java
index 897af0e..c8165b5 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/override/DefaultPriceOverride.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/override/DefaultPriceOverride.java
@@ -18,7 +18,6 @@
 package org.killbill.billing.catalog.override;
 
 import java.util.List;
-import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import org.joda.time.DateTime;
@@ -29,14 +28,13 @@ import org.killbill.billing.catalog.DefaultPlan;
 import org.killbill.billing.catalog.DefaultPlanPhase;
 import org.killbill.billing.catalog.DefaultPlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.CatalogApiException;
-import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanPhase;
 import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.StaticCatalog;
+import org.killbill.billing.catalog.caching.OverriddenPlanCache;
 import org.killbill.billing.catalog.dao.CatalogOverrideDao;
-import org.killbill.billing.catalog.dao.CatalogOverridePhaseDefinitionModelDao;
 import org.killbill.billing.catalog.dao.CatalogOverridePlanDefinitionModelDao;
 
 import com.google.common.base.Predicate;
@@ -48,10 +46,12 @@ public class DefaultPriceOverride implements PriceOverride {
     public static final Pattern CUSTOM_PLAN_NAME_PATTERN = Pattern.compile("(.*)-(\\d+)$");
 
     private final CatalogOverrideDao overrideDao;
+    private final OverriddenPlanCache overriddenPlanCache;
 
     @Inject
-    public DefaultPriceOverride(final CatalogOverrideDao overrideDao) {
+    public DefaultPriceOverride(final CatalogOverrideDao overrideDao, final OverriddenPlanCache overriddenPlanCache) {
         this.overrideDao = overrideDao;
+        this.overriddenPlanCache = overriddenPlanCache;
     }
 
     @Override
@@ -66,7 +66,7 @@ public class DefaultPriceOverride implements PriceOverride {
                     if (input.getPhaseName() != null) {
                         return input.getPhaseName().equals(curPhase.getName());
                     }
-                    // If the phaseName was not passed, we infer by matching the phaseType. This obvously would not work in a case where
+                    // If the phaseName was not passed, we infer by matching the phaseType. This obviously would not work in a case where
                     // a plan is defined with multiple phases of the same type.
                     final PlanPhaseSpecifier curPlanPhaseSpecifier = input.getPlanPhaseSpecifier();
                     if (curPlanPhaseSpecifier.getPhaseType().equals(curPhase.getPhaseType())) {
@@ -105,39 +105,6 @@ public class DefaultPriceOverride implements PriceOverride {
 
     @Override
     public DefaultPlan getOverriddenPlan(final String planName, final StaticCatalog catalog, final InternalTenantContext context) throws CatalogApiException {
-
-        final Matcher m = CUSTOM_PLAN_NAME_PATTERN.matcher(planName);
-        if (!m.matches()) {
-            throw new CatalogApiException(ErrorCode.CAT_NO_SUCH_PLAN, planName);
-        }
-        final String parentPlanName = m.group(1);
-        final Long planDefRecordId = Long.parseLong(m.group(2));
-
-        final List<CatalogOverridePhaseDefinitionModelDao> phaseDefs = overrideDao.getOverriddenPlanPhases(planDefRecordId, context);
-        final DefaultPlan defaultPlan = (DefaultPlan) catalog.findCurrentPlan(parentPlanName);
-
-        final PlanPhasePriceOverride[] overrides = createOverrides(defaultPlan, phaseDefs);
-        return new DefaultPlan(planName, defaultPlan, overrides);
+        return overriddenPlanCache.getOverriddenPlan(planName, catalog, context);
     }
-
-    private PlanPhasePriceOverride[] createOverrides(final Plan defaultPlan, final List<CatalogOverridePhaseDefinitionModelDao> phaseDefs) {
-
-        final PlanPhasePriceOverride[] result = new PlanPhasePriceOverride[defaultPlan.getAllPhases().length];
-
-        for (int i = 0; i < defaultPlan.getAllPhases().length; i++) {
-
-            final PlanPhase curPhase = defaultPlan.getAllPhases()[i];
-            final CatalogOverridePhaseDefinitionModelDao overriddenPhase = Iterables.tryFind(phaseDefs, new Predicate<CatalogOverridePhaseDefinitionModelDao>() {
-                @Override
-                public boolean apply(final CatalogOverridePhaseDefinitionModelDao input) {
-                    return input.getParentPhaseName().equals(curPhase.getName());
-                }
-            }).orNull();
-            result[i] = (overriddenPhase != null) ?
-                        new DefaultPlanPhasePriceOverride(curPhase.getName(), Currency.valueOf(overriddenPhase.getCurrency()), overriddenPhase.getFixedPrice(), overriddenPhase.getRecurringPrice()) :
-                        null;
-        }
-        return result;
-    }
-
 }
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 542b14e..767cc94 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
@@ -34,10 +34,12 @@ public @interface Cachable {
     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 {
+
         /* Mapping from object 'id (UUID)' -> object 'recordId (Long' */
         RECORD_ID(RECORD_ID_CACHE_NAME, false),
 
@@ -63,7 +65,10 @@ public @interface Cachable {
         TENANT_OVERDUE_CONFIG(TENANT_OVERDUE_CONFIG_CACHE_NAME, false),
 
         /* Tenant overdue config cache */
-        TENANT_KV(TENANT_KV_CACHE_NAME, false);
+        TENANT_KV(TENANT_KV_CACHE_NAME, false),
+
+        /* Overwritten plans  */
+        OVERRIDDEN_PLAN(OVERRIDDEN_PLAN_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 3698c4b..7afab31 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
@@ -48,7 +48,8 @@ public class EhCacheCacheManagerProvider implements Provider<CacheManager> {
                                        final AuditLogViaHistoryCacheLoader auditLogViaHistoryCacheLoader,
                                        final TenantCatalogCacheLoader tenantCatalogCacheLoader,
                                        final TenantOverdueConfigCacheLoader tenantOverdueConfigCacheLoader,
-                                       final TenantKVCacheLoader tenantKVCacheLoader) {
+                                       final TenantKVCacheLoader tenantKVCacheLoader,
+                                       final OverriddenPlanCacheLoader overriddenPlanCacheLoader) {
         this.cacheConfig = cacheConfig;
         cacheLoaders.add(recordIdCacheLoader);
         cacheLoaders.add(accountRecordIdCacheLoader);
@@ -59,6 +60,7 @@ public class EhCacheCacheManagerProvider implements Provider<CacheManager> {
         cacheLoaders.add(tenantCatalogCacheLoader);
         cacheLoaders.add(tenantOverdueConfigCacheLoader);
         cacheLoaders.add(tenantKVCacheLoader);
+        cacheLoaders.add(overriddenPlanCacheLoader);
     }
 
     @Override
diff --git a/util/src/main/java/org/killbill/billing/util/cache/OverriddenPlanCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/OverriddenPlanCacheLoader.java
new file mode 100644
index 0000000..ab2e833
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/cache/OverriddenPlanCacheLoader.java
@@ -0,0 +1,91 @@
+/*
+ * 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.List;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.StaticCatalog;
+import org.killbill.billing.tenant.api.TenantInternalApi;
+import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Singleton
+public class OverriddenPlanCacheLoader extends BaseCacheLoader {
+
+    private final Logger log = LoggerFactory.getLogger(OverriddenPlanCacheLoader.class);
+
+
+    @Inject
+    public OverriddenPlanCacheLoader(final TenantInternalApi tenantApi) {
+        super();
+    }
+
+    @Override
+    public CacheType getCacheType() {
+        return CacheType.OVERRIDDEN_PLAN;
+    }
+
+    @Override
+    public Object load(final Object key, final Object argument) {
+        checkCacheLoaderStatus();
+
+        if (!(key instanceof String)) {
+            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().length != 2) {
+            throw new IllegalArgumentException("Invalid arguments for overridden plans");
+        }
+        if (!(cacheLoaderArgument.getArgs()[0] instanceof LoaderCallback)) {
+            throw new IllegalArgumentException("Invalid arguments for overridden plans: missing loaderCallback from argument");
+        }
+
+        if (!(cacheLoaderArgument.getArgs()[1] instanceof StaticCatalog)) {
+            throw new IllegalArgumentException("Invalid arguments for overridden plans: missing catalog from argument");
+        }
+
+
+        final String planName = (String) key;
+        final LoaderCallback callback = (LoaderCallback) cacheLoaderArgument.getArgs()[0];
+        final StaticCatalog catalog = (StaticCatalog) cacheLoaderArgument.getArgs()[1];
+        final InternalTenantContext internalTenantContext = ((CacheLoaderArgument) argument).getInternalTenantContext();
+        try {
+            log.info("Loading overridden plan {} for tenant {}", planName, internalTenantContext.getTenantRecordId());
+
+            return callback.loadPlan(planName, catalog, internalTenantContext);
+        } catch (final CatalogApiException e) {
+            throw new IllegalStateException(String.format("Failed to load overridden plan for tenant %s : %s",
+                                                          planName, internalTenantContext.getTenantRecordId()), e);
+        }
+    }
+
+    public interface LoaderCallback {
+        public Object loadPlan(final String planName, final StaticCatalog catalog, final InternalTenantContext context) throws CatalogApiException;
+    }
+}
diff --git a/util/src/main/resources/ehcache.xml b/util/src/main/resources/ehcache.xml
index bf0d8cb..7769245 100644
--- a/util/src/main/resources/ehcache.xml
+++ b/util/src/main/resources/ehcache.xml
@@ -155,5 +155,19 @@
                 properties=""/>
     </cache>
 
+    <cache name="overridden-plan"
+           maxElementsInMemory="1000"
+           maxElementsOnDisk="0"
+           overflowToDisk="false"
+           diskPersistent="false"
+           memoryStoreEvictionPolicy="LFU"
+           statistics="true"
+            >
+        <cacheEventListenerFactory
+                class="org.killbill.billing.util.cache.ExpirationListenerFactory"
+                properties=""/>
+    </cache>
+
+
 </ehcache>