killbill-memoizeit

util: switch to Ehcache 3 jars The integration mostly relies

3/22/2017 4:23:02 AM

Details

diff --git a/profiles/killbill/pom.xml b/profiles/killbill/pom.xml
index 1e98233..deb1bf0 100644
--- a/profiles/killbill/pom.xml
+++ b/profiles/killbill/pom.xml
@@ -172,10 +172,6 @@
         </dependency>
         <dependency>
             <groupId>org.apache.shiro</groupId>
-            <artifactId>shiro-ehcache</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.shiro</groupId>
             <artifactId>shiro-guice</artifactId>
         </dependency>
         <dependency>
@@ -228,6 +224,10 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>org.ehcache.integrations.shiro</groupId>
+            <artifactId>shiro-ehcache3</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.javassist</groupId>
             <artifactId>javassist</artifactId>
             <scope>runtime</scope>
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillJdbcTenantRealmProvider.java b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillJdbcTenantRealmProvider.java
index d53b571..5774818 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillJdbcTenantRealmProvider.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillJdbcTenantRealmProvider.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 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
@@ -23,7 +23,6 @@ import javax.sql.DataSource;
 import org.apache.shiro.cache.CacheManager;
 import org.killbill.billing.server.security.KillbillJdbcTenantRealm;
 import org.killbill.billing.util.config.definition.SecurityConfig;
-import org.killbill.billing.util.glue.ShiroEhCacheInstrumentor;
 
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -32,14 +31,12 @@ public class KillbillJdbcTenantRealmProvider implements Provider<KillbillJdbcTen
 
     private final SecurityConfig securityConfig;
     private final CacheManager cacheManager;
-    private final ShiroEhCacheInstrumentor ehCacheInstrumentor;
     private final DataSource dataSource;
 
     @Inject
-    public KillbillJdbcTenantRealmProvider(final SecurityConfig securityConfig, final CacheManager cacheManager, final ShiroEhCacheInstrumentor ehCacheInstrumentor, @Named(KillbillPlatformModule.SHIRO_DATA_SOURCE_ID_NAMED) final DataSource dataSource) {
+    public KillbillJdbcTenantRealmProvider(final SecurityConfig securityConfig, final CacheManager cacheManager, @Named(KillbillPlatformModule.SHIRO_DATA_SOURCE_ID_NAMED) final DataSource dataSource) {
         this.securityConfig = securityConfig;
         this.cacheManager = cacheManager;
-        this.ehCacheInstrumentor = ehCacheInstrumentor;
         this.dataSource = dataSource;
     }
 
@@ -52,9 +49,6 @@ public class KillbillJdbcTenantRealmProvider implements Provider<KillbillJdbcTen
         // automatically configured with the EhCache manager (see EhCacheManagerProvider)
         killbillJdbcTenantRealm.setCacheManager(cacheManager);
 
-        // Instrument the cache
-        ehCacheInstrumentor.instrument(killbillJdbcTenantRealm);
-
         return killbillJdbcTenantRealm;
     }
 }
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
index a623ff3..4d8f5c1 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 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
@@ -27,9 +27,7 @@ import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
 import org.apache.shiro.authc.pam.ModularRealmAuthenticatorWith540;
 import org.apache.shiro.cache.CacheManager;
 import org.apache.shiro.guice.web.ShiroWebModuleWith435;
-import org.apache.shiro.realm.Realm;
 import org.apache.shiro.session.mgt.SessionManager;
-import org.apache.shiro.session.mgt.eis.CachingSessionDAO;
 import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
 import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
 import org.apache.shiro.web.mgt.WebSecurityManager;
@@ -39,20 +37,17 @@ import org.killbill.billing.jaxrs.resources.JaxrsResource;
 import org.killbill.billing.server.security.FirstSuccessfulStrategyWith540;
 import org.killbill.billing.server.security.KillbillJdbcTenantRealm;
 import org.killbill.billing.util.config.definition.RbacConfig;
-import org.killbill.billing.util.glue.EhCacheManagerProvider;
+import org.killbill.billing.util.glue.EhcacheShiroManagerProvider;
 import org.killbill.billing.util.glue.IniRealmProvider;
 import org.killbill.billing.util.glue.JDBCSessionDaoProvider;
 import org.killbill.billing.util.glue.KillBillShiroModule;
-import org.killbill.billing.util.glue.ShiroEhCacheInstrumentor;
 import org.killbill.billing.util.security.shiro.dao.JDBCSessionDao;
 import org.killbill.billing.util.security.shiro.realm.KillBillJdbcRealm;
 import org.killbill.billing.util.security.shiro.realm.KillBillJndiLdapRealm;
 import org.skife.config.ConfigSource;
 import org.skife.config.ConfigurationObjectFactory;
 
-import com.google.inject.Inject;
 import com.google.inject.Key;
-import com.google.inject.Provider;
 import com.google.inject.TypeLiteral;
 import com.google.inject.binder.AnnotatedBindingBuilder;
 import com.google.inject.matcher.AbstractMatcher;
@@ -73,16 +68,9 @@ public class KillBillShiroWebModule extends ShiroWebModuleWith435 {
     }
 
     @Override
-    public void configure() {
-        super.configure();
-
-        bind(ShiroEhCacheInstrumentor.class).asEagerSingleton();
-    }
-
-    @Override
     protected void configureShiroWeb() {
         // Magic provider to configure the cache manager
-        bind(CacheManager.class).toProvider(EhCacheManagerProvider.class).asEagerSingleton();
+        bind(CacheManager.class).toProvider(EhcacheShiroManagerProvider.class).asEagerSingleton();
 
         configureShiroForRBAC();
 
@@ -106,7 +94,7 @@ public class KillBillShiroWebModule extends ShiroWebModuleWith435 {
                              return Matchers.subclassesOf(WebSecurityManager.class).matches(o.getRawType());
                          }
                      },
-                     new DefaultWebSecurityManagerTypeListener(getProvider(ShiroEhCacheInstrumentor.class)));
+                     new DefaultWebSecurityManagerTypeListener());
 
         if (KillBillShiroModule.isRBACEnabled()) {
             addFilterChain(JaxrsResource.PREFIX + "/**", Key.get(CorsBasicHttpAuthenticationFilter.class));
@@ -143,30 +131,16 @@ public class KillBillShiroWebModule extends ShiroWebModuleWith435 {
 
     private static final class DefaultWebSecurityManagerTypeListener implements TypeListener {
 
-        private final Provider<ShiroEhCacheInstrumentor> instrumentorProvider;
-
-        @Inject
-        public DefaultWebSecurityManagerTypeListener(final Provider<ShiroEhCacheInstrumentor> instrumentorProvider) {
-            this.instrumentorProvider = instrumentorProvider;
-        }
-
         @Override
         public <I> void hear(final TypeLiteral<I> typeLiteral, final TypeEncounter<I> typeEncounter) {
             typeEncounter.register(new InjectionListener<I>() {
                 @Override
                 public void afterInjection(final Object o) {
-                    final ShiroEhCacheInstrumentor ehCacheInstrumentor = instrumentorProvider.get();
-                    ehCacheInstrumentor.instrument(CachingSessionDAO.ACTIVE_SESSION_CACHE_NAME);
-
                     final DefaultWebSecurityManager webSecurityManager = (DefaultWebSecurityManager) o;
                     if (webSecurityManager.getAuthenticator() instanceof ModularRealmAuthenticator) {
                         final ModularRealmAuthenticator authenticator = (ModularRealmAuthenticator) webSecurityManager.getAuthenticator();
                         authenticator.setAuthenticationStrategy(new FirstSuccessfulStrategyWith540());
                         webSecurityManager.setAuthenticator(new ModularRealmAuthenticatorWith540(authenticator));
-
-                        for (final Realm realm : webSecurityManager.getRealms()) {
-                            ehCacheInstrumentor.instrument(realm);
-                        }
                     }
                 }
             });

util/pom.xml 20(+12 -8)

diff --git a/util/pom.xml b/util/pom.xml
index 7743895..1a4974b 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -104,7 +104,11 @@
         </dependency>
         <dependency>
             <groupId>io.dropwizard.metrics</groupId>
-            <artifactId>metrics-ehcache</artifactId>
+            <artifactId>metrics-jcache</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.cache</groupId>
+            <artifactId>cache-api</artifactId>
         </dependency>
         <dependency>
             <groupId>javax.inject</groupId>
@@ -126,10 +130,6 @@
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>net.sf.ehcache</groupId>
-            <artifactId>ehcache</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.antlr</groupId>
             <artifactId>stringtemplate</artifactId>
         </dependency>
@@ -143,11 +143,15 @@
         </dependency>
         <dependency>
             <groupId>org.apache.shiro</groupId>
-            <artifactId>shiro-ehcache</artifactId>
+            <artifactId>shiro-guice</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.apache.shiro</groupId>
-            <artifactId>shiro-guice</artifactId>
+            <groupId>org.ehcache</groupId>
+            <artifactId>ehcache</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.ehcache.integrations.shiro</groupId>
+            <artifactId>shiro-ehcache3</artifactId>
         </dependency>
         <dependency>
             <groupId>org.flywaydb</groupId>
diff --git a/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcherProvider.java b/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcherProvider.java
index 302176c..7d9ce76 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcherProvider.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcherProvider.java
@@ -22,6 +22,8 @@ import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Set;
 
+import javax.cache.Cache;
+import javax.cache.CacheManager;
 import javax.inject.Inject;
 import javax.inject.Provider;
 
@@ -29,9 +31,6 @@ import org.killbill.billing.util.cache.Cachable.CacheType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import net.sf.ehcache.CacheManager;
-import net.sf.ehcache.Ehcache;
-
 // Build the abstraction layer between EhCache and Kill Bill
 public class CacheControllerDispatcherProvider implements Provider<CacheControllerDispatcher> {
 
@@ -53,7 +52,7 @@ public class CacheControllerDispatcherProvider implements Provider<CacheControll
         for (final BaseCacheLoader cacheLoader : cacheLoaders) {
             final CacheType cacheType = cacheLoader.getCacheType();
 
-            final Ehcache cache = cacheManager.getEhcache(cacheType.getCacheName());
+            final Cache cache = cacheManager.getCache(cacheType.getCacheName(), cacheType.getKeyType(), cacheType.getValueType());
             if (cache == null) {
                 logger.warn("Cache for cacheName='{}' not configured - check your ehcache.xml", cacheLoader.getCacheType().getCacheName());
                 continue;
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 6372e3b..92e8e42 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,76 +18,73 @@
 
 package org.killbill.billing.util.cache;
 
-import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
+
+import javax.cache.Cache;
+import javax.cache.Cache.Entry;
 
 import org.killbill.billing.util.cache.Cachable.CacheType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Function;
-import net.sf.ehcache.Ehcache;
-import net.sf.ehcache.Element;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 
 public class EhCacheBasedCacheController<K, V> implements CacheController<K, V> {
 
     private static final Logger logger = LoggerFactory.getLogger(EhCacheBasedCacheController.class);
 
-    private final Ehcache cache;
+    private final Cache<K, V> cache;
     private final BaseCacheLoader<K, V> baseCacheLoader;
 
-    public EhCacheBasedCacheController(final Ehcache cache, final BaseCacheLoader<K, V> baseCacheLoader) {
+    public EhCacheBasedCacheController(final Cache<K, V> cache, final BaseCacheLoader<K, V> baseCacheLoader) {
         this.cache = cache;
         this.baseCacheLoader = baseCacheLoader;
     }
 
     @Override
     public List<K> getKeys() {
-        return cache.getKeys();
+        final Iterable<K> kIterable = Iterables.<Entry<K, V>, K>transform(cache,
+                                                                          new Function<Entry<K, V>, K>() {
+                                                                              @Override
+                                                                              public K apply(final Entry<K, V> input) {
+                                                                                  return input.getKey();
+                                                                              }
+                                                                          });
+        return ImmutableList.<K>copyOf(kIterable);
     }
 
     @Override
     public boolean isKeyInCache(final K key) {
-        return cache.isKeyInCache(key);
+        return cache.containsKey(key);
     }
 
     @Override
     public V get(final K key, final CacheLoaderArgument cacheLoaderArgument) {
-        checkKey(key);
-
         final V value;
         if (!isKeyInCache(key)) {
             value = computeAndCacheValue(key, cacheLoaderArgument);
         } else {
-            final Element element = cache.get(key);
-            if (element == null) {
-                value = null;
-            } else if (element.isExpired()) {
-                value = computeAndCacheValue(key, cacheLoaderArgument);
-            } else {
-                value = (V) element.getObjectValue();
-            }
+            value = cache.get(key);
         }
 
         if (value == null || value.equals(BaseCacheLoader.EMPTY_VALUE_PLACEHOLDER)) {
             return null;
         } else {
-            checkValue(value);
             return value;
         }
     }
 
     @Override
     public void putIfAbsent(final K key, final V value) {
-        checkKey(key);
-        checkValue(value);
-        cache.putIfAbsent(new Element(key, value));
+        cache.putIfAbsent(key, value);
     }
 
     @Override
     public boolean remove(final K key) {
-        checkKey(key);
         if (isKeyInCache(key)) {
             cache.remove(key);
             return true;
@@ -98,7 +95,7 @@ public class EhCacheBasedCacheController<K, V> implements CacheController<K, V> 
 
     @Override
     public void remove(final Function<K, Boolean> keyMatcher) {
-        final Collection<K> toRemove = new HashSet<K>();
+        final Set<K> toRemove = new HashSet<K>();
         for (final Object key : getKeys()) {
             if (keyMatcher.apply((K) key) == Boolean.TRUE) {
                 toRemove.add((K) key);
@@ -109,12 +106,12 @@ public class EhCacheBasedCacheController<K, V> implements CacheController<K, V> 
 
     @Override
     public void removeAll() {
-        cache.removeAll();
+        cache.clear();
     }
 
     @Override
     public int size() {
-        return cache.getSize();
+        return Iterables.<Cache.Entry<K, V>>size(cache);
     }
 
     @Override
@@ -123,8 +120,6 @@ public class EhCacheBasedCacheController<K, V> implements CacheController<K, V> 
     }
 
     private V computeAndCacheValue(final K key, final CacheLoaderArgument cacheLoaderArgument) {
-        checkKey(key);
-
         final V value;
         try {
             value = baseCacheLoader.compute(key, cacheLoaderArgument);
@@ -137,29 +132,9 @@ public class EhCacheBasedCacheController<K, V> implements CacheController<K, V> 
             return null;
         }
 
-        checkValue(value);
-
         // Race condition, we may compute it for nothing
-        cache.putIfAbsent(new Element(key, value));
+        cache.putIfAbsent(key, value);
 
         return value;
     }
-
-    private void checkKey(final K keyObject) {
-        if (keyObject == null) {
-            throw new NullPointerException();
-        }
-        if (!getCacheType().getKeyType().isAssignableFrom(keyObject.getClass())) {
-            throw new ClassCastException("Invalid key type, expected : " + getCacheType().getKeyType().getName() + " but was : " + keyObject.getClass().getName());
-        }
-    }
-
-    private void checkValue(final V valueObject) {
-        if (valueObject == null) {
-            throw new NullPointerException();
-        }
-        if (!getCacheType().getValueType().isAssignableFrom(valueObject.getClass())) {
-            throw new ClassCastException("Invalid value type, expected : " + getCacheType().getValueType().getName() + " but was : " + valueObject.getClass().getName());
-        }
-    }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/glue/CacheModule.java b/util/src/main/java/org/killbill/billing/util/glue/CacheModule.java
index 4ea728c..d4ddf25 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/CacheModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/CacheModule.java
@@ -18,6 +18,8 @@
 
 package org.killbill.billing.util.glue;
 
+import javax.cache.CacheManager;
+
 import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.cache.AccountBCDCacheLoader;
 import org.killbill.billing.util.cache.AccountRecordIdCacheLoader;
@@ -26,7 +28,6 @@ import org.killbill.billing.util.cache.AuditLogViaHistoryCacheLoader;
 import org.killbill.billing.util.cache.BaseCacheLoader;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.cache.CacheControllerDispatcherProvider;
-import org.killbill.billing.util.cache.EhCacheCacheManagerProvider;
 import org.killbill.billing.util.cache.ImmutableAccountCacheLoader;
 import org.killbill.billing.util.cache.ObjectIdCacheLoader;
 import org.killbill.billing.util.cache.OverriddenPlanCacheLoader;
@@ -42,7 +43,6 @@ import org.killbill.billing.util.config.definition.EhCacheConfig;
 import org.skife.config.ConfigurationObjectFactory;
 
 import com.google.inject.multibindings.Multibinder;
-import net.sf.ehcache.CacheManager;
 
 public class CacheModule extends KillBillModule {
 
@@ -56,7 +56,7 @@ public class CacheModule extends KillBillModule {
         bind(EhCacheConfig.class).toInstance(config);
 
         // EhCache specifics
-        bind(CacheManager.class).toProvider(EhCacheCacheManagerProvider.class).asEagerSingleton();
+        bind(CacheManager.class).toProvider(Eh107CacheManagerProvider.class).asEagerSingleton();
 
         // Kill Bill generic cache dispatcher
         bind(CacheControllerDispatcher.class).toProvider(CacheControllerDispatcherProvider.class).asEagerSingleton();
diff --git a/util/src/main/java/org/killbill/billing/util/glue/CacheProviderBase.java b/util/src/main/java/org/killbill/billing/util/glue/CacheProviderBase.java
new file mode 100644
index 0000000..24f2dbf
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/glue/CacheProviderBase.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 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.glue;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import javax.cache.configuration.MutableConfiguration;
+
+import org.killbill.billing.util.config.definition.EhCacheConfig;
+import org.killbill.xmlloader.UriAccessor;
+
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.MetricFilter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.jcache.JCacheGaugeSet;
+
+abstract class CacheProviderBase {
+
+    private static final String PROP_METRIC_REG_JCACHE_STATISTICS = "jcache.statistics";
+
+    private final MetricRegistry metricRegistry;
+
+    final URL xmlConfigurationURL;
+
+    CacheProviderBase(final MetricRegistry metricRegistry, final EhCacheConfig cacheConfig) {
+        this.metricRegistry = metricRegistry;
+
+        try {
+            xmlConfigurationURL = UriAccessor.toURL(cacheConfig.getCacheConfigLocation());
+        } catch (final IOException e) {
+            throw new RuntimeException(e);
+        } catch (final URISyntaxException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    <K, V> Cache<K, V> createCache(final CacheManager cacheManager, final String cacheName, final Class<K> keyType, final Class<V> valueType) {
+        // Make sure we start from a clean state - this is mainly useful for tests
+        cacheManager.destroyCache(cacheName);
+
+        // All other configuration options come from the ehcache.xml
+        final MutableConfiguration<K, V> configuration = new MutableConfiguration<K, V>().setTypes(keyType, valueType)
+                                                                                         .setStoreByValue(false); // Store by reference to avoid copying large objects (e.g. catalog)
+        final Cache<K, V> cache = cacheManager.createCache(cacheName, configuration);
+
+        // Re-create the metrics to support dynamically created caches (e.g. for Shiro)
+        metricRegistry.removeMatching(new MetricFilter() {
+            @Override
+            public boolean matches(final String name, final Metric metric) {
+                return name != null && name.startsWith(PROP_METRIC_REG_JCACHE_STATISTICS);
+            }
+        });
+        metricRegistry.register(PROP_METRIC_REG_JCACHE_STATISTICS, new JCacheGaugeSet());
+
+        return cache;
+    }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/glue/EhcacheShiroManagerProvider.java b/util/src/main/java/org/killbill/billing/util/glue/EhcacheShiroManagerProvider.java
new file mode 100644
index 0000000..1af8a98
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/glue/EhcacheShiroManagerProvider.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 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.glue;
+
+import java.lang.reflect.Field;
+
+import javax.cache.CacheManager;
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+import org.apache.shiro.cache.Cache;
+import org.apache.shiro.cache.CacheException;
+import org.apache.shiro.mgt.DefaultSecurityManager;
+import org.apache.shiro.mgt.SecurityManager;
+import org.ehcache.integrations.shiro.EhcacheShiro;
+import org.ehcache.integrations.shiro.EhcacheShiroManager;
+import org.killbill.billing.util.config.definition.EhCacheConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.codahale.metrics.MetricRegistry;
+
+public class EhcacheShiroManagerProvider extends CacheProviderBase implements Provider<EhcacheShiroManager> {
+
+    private final SecurityManager securityManager;
+    private final CacheManager eh107CacheManager;
+    private final org.ehcache.CacheManager ehcacheCacheManager;
+
+    @Inject
+    public EhcacheShiroManagerProvider(final SecurityManager securityManager,
+                                       final CacheManager eh107CacheManager,
+                                       final MetricRegistry metricRegistry,
+                                       final EhCacheConfig cacheConfig) {
+        super(metricRegistry, cacheConfig);
+        this.securityManager = securityManager;
+        this.eh107CacheManager = eh107CacheManager;
+        this.ehcacheCacheManager = getEhcacheManager();
+    }
+
+    @Override
+    public EhcacheShiroManager get() {
+        final EhcacheShiroManager shiroEhCacheManager = new EhcacheShiroManagerWrapper(this);
+        // Same EhCache manager instance as the rest of the system
+        shiroEhCacheManager.setCacheManager(ehcacheCacheManager);
+
+        if (securityManager instanceof DefaultSecurityManager) {
+            // For RBAC only (see also KillbillJdbcTenantRealmProvider)
+            ((DefaultSecurityManager) securityManager).setCacheManager(shiroEhCacheManager);
+        }
+
+        return shiroEhCacheManager;
+    }
+
+    // Shiro isn't JCache compatible
+    private org.ehcache.CacheManager getEhcacheManager() {
+        try {
+            final Field f = eh107CacheManager.getClass().getDeclaredField("ehCacheManager");
+            f.setAccessible(true);
+
+            return (org.ehcache.CacheManager) f.get(eh107CacheManager);
+        } catch (final IllegalAccessException e) {
+            throw new RuntimeException(e);
+        } catch (final NoSuchFieldException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    // Custom createCache implementation going through JCache layer to enable stats, etc.
+    private final class EhcacheShiroManagerWrapper extends EhcacheShiroManager {
+
+        private final Logger log = LoggerFactory.getLogger(EhcacheShiroManagerWrapper.class);
+
+        private final EhcacheShiroManagerProvider ehcacheShiroManagerProvider;
+
+        EhcacheShiroManagerWrapper(final EhcacheShiroManagerProvider ehcacheShiroManagerProvider) {
+            this.ehcacheShiroManagerProvider = ehcacheShiroManagerProvider;
+        }
+
+        public <K, V> Cache<K, V> getCache(final String name) throws CacheException {
+            log.trace("Acquiring EhcacheShiro instance named [{}]", name);
+
+            org.ehcache.Cache<Object, Object> cache = getCacheManager().getCache(name, Object.class, Object.class);
+
+            if (cache == null) {
+                log.info("Cache with name {} does not yet exist.  Creating now.", name);
+                ehcacheShiroManagerProvider.createCache(eh107CacheManager, name, Object.class, Object.class);
+                cache = getCacheManager().getCache(name, Object.class, Object.class);
+                log.info("Added EhcacheShiro named [{}]", name);
+            } else {
+                log.info("Using existing EhcacheShiro named [{}]", name);
+            }
+
+            return new EhcacheShiro<K, V>(cache);
+        }
+    }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java b/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
index 4e48913..bd9ceb2 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
@@ -86,7 +86,7 @@ public class KillBillShiroModule extends ShiroModule {
         super.bindSecurityManager(bind);
 
         // Magic provider to configure the cache manager
-        bind(CacheManager.class).toProvider(EhCacheManagerProvider.class).asEagerSingleton();
+        bind(CacheManager.class).toProvider(EhcacheShiroManagerProvider.class).asEagerSingleton();
     }
 
     @Override
diff --git a/util/src/main/resources/ehcache.xml b/util/src/main/resources/ehcache.xml
index 61d098d..faf95e5 100644
--- a/util/src/main/resources/ehcache.xml
+++ b/util/src/main/resources/ehcache.xml
@@ -2,8 +2,8 @@
 
 <!--
   ~ Copyright 2010-2014 Ning, Inc.
-  ~ Copyright 2014-2016 Groupon, Inc
-  ~ Copyright 2014-2016 The Billing Project, LLC
+  ~ Copyright 2014-2017 Groupon, Inc
+  ~ Copyright 2014-2017 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
@@ -18,221 +18,26 @@
   ~ under the License.
   -->
 
-<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:noNamespaceSchemaLocation="ehcache.xsd">
-
-    <defaultCache
-            maxElementsInMemory="100000"
-            maxElementsOnDisk="0"
-            eternal="true"
-            overflowToDisk="false"
-            diskPersistent="false"
-            memoryStoreEvictionPolicy="LFU"
-            statistics="true"
-            />
-
-    <cache name="record-id"
-           maxElementsInMemory="100000"
-           maxElementsOnDisk="0"
-           eternal="true"
-           overflowToDisk="false"
-           diskPersistent="false"
-           memoryStoreEvictionPolicy="LFU"
-           statistics="true"
-            >
-        <cacheEventListenerFactory
-                class="org.killbill.billing.util.cache.ExpirationListenerFactory"
-                properties=""/>
-    </cache>
-
-    <cache name="tenant-record-id"
-           maxElementsInMemory="100000"
-           maxElementsOnDisk="0"
-           eternal="true"
-           overflowToDisk="false"
-           diskPersistent="false"
-           memoryStoreEvictionPolicy="LFU"
-           statistics="true"
-            >
-        <cacheEventListenerFactory
-                class="org.killbill.billing.util.cache.ExpirationListenerFactory"
-                properties=""/>
-    </cache>
-
-    <cache name="account-record-id"
-           maxElementsInMemory="100000"
-           maxElementsOnDisk="0"
-           eternal="true"
-           overflowToDisk="false"
-           diskPersistent="false"
-           memoryStoreEvictionPolicy="LFU"
-           statistics="true"
-            >
-        <cacheEventListenerFactory
-                class="org.killbill.billing.util.cache.ExpirationListenerFactory"
-                properties=""/>
-    </cache>
-
-    <cache name="object-id"
-           maxElementsInMemory="100000"
-           maxElementsOnDisk="0"
-           eternal="true"
-           overflowToDisk="false"
-           diskPersistent="false"
-           memoryStoreEvictionPolicy="LFU"
-           statistics="true"
-            >
-        <cacheEventListenerFactory
-                class="org.killbill.billing.util.cache.ExpirationListenerFactory"
-                properties=""/>
-    </cache>
-
-
-    <cache name="audit-log"
-           maxElementsInMemory="500000"
-           maxElementsOnDisk="0"
-           timeToIdleSeconds="600"
-           timeToLiveSeconds="600"
-           overflowToDisk="false"
-           diskPersistent="false"
-           memoryStoreEvictionPolicy="LFU"
-           statistics="true"
-            >
-        <cacheEventListenerFactory
-                class="org.killbill.billing.util.cache.ExpirationListenerFactory"
-                properties=""/>
-    </cache>
-
-    <cache name="audit-log-via-history"
-           maxElementsInMemory="500000"
-           maxElementsOnDisk="0"
-           timeToIdleSeconds="600"
-           timeToLiveSeconds="600"
-           overflowToDisk="false"
-           diskPersistent="false"
-           memoryStoreEvictionPolicy="LFU"
-           statistics="true"
-            >
-        <cacheEventListenerFactory
-                class="org.killbill.billing.util.cache.ExpirationListenerFactory"
-                properties=""/>
-    </cache>
-
-    <cache name="tenant-catalog"
-           maxElementsInMemory="1000"
-           maxElementsOnDisk="0"
-           overflowToDisk="false"
-           diskPersistent="false"
-           memoryStoreEvictionPolicy="LFU"
-           statistics="true"
-            >
-        <cacheEventListenerFactory
-                class="org.killbill.billing.util.cache.ExpirationListenerFactory"
-                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>
-
-    <cache name="tenant-config"
-           maxElementsInMemory="1000"
-           maxElementsOnDisk="0"
-           overflowToDisk="false"
-           diskPersistent="false"
-           memoryStoreEvictionPolicy="LFU"
-           statistics="true">
-        <cacheEventListenerFactory
-                class="org.killbill.billing.util.cache.ExpirationListenerFactory"
-                properties=""/>
-    </cache>
-
-    <cache name="tenant-kv"
-           maxElementsInMemory="1000"
-           maxElementsOnDisk="0"
-           overflowToDisk="false"
-           diskPersistent="false"
-           memoryStoreEvictionPolicy="LFU"
-           statistics="true"
-            >
-        <cacheEventListenerFactory
-                class="org.killbill.billing.util.cache.ExpirationListenerFactory"
-                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>
-
-    <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>
-
-
-    <cache name="tenant"
-           maxElementsInMemory="100"
-           maxElementsOnDisk="0"
-           overflowToDisk="false"
-           diskPersistent="false"
-           memoryStoreEvictionPolicy="LFU"
-           statistics="true"
-            >
-        <cacheEventListenerFactory
-                class="org.killbill.billing.util.cache.ExpirationListenerFactory"
-                properties=""/>
-    </cache>
-
-    <cache name="tenant-payment-state-machine-config"
-           maxElementsInMemory="100"
-           maxElementsOnDisk="0"
-           overflowToDisk="false"
-           diskPersistent="false"
-           memoryStoreEvictionPolicy="LFU"
-           statistics="true"
-            >
-        <cacheEventListenerFactory
-                class="org.killbill.billing.util.cache.ExpirationListenerFactory"
-                properties=""/>
-    </cache>
-
-</ehcache>
+<ehcache:config xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
+                xmlns:ehcache='http://www.ehcache.org/v3'
+                xmlns:jsr107='http://www.ehcache.org/v3/jsr107'
+                xsi:schemaLocation="http://www.ehcache.org/v3
+                                    http://www.ehcache.org/schema/ehcache-core-3.0.xsd
+                                    http://www.ehcache.org/v3/jsr107
+                                    http://www.ehcache.org/schema/ehcache-107-ext-3.0.xsd">
+    <ehcache:service>
+        <jsr107:defaults default-template="defaultCacheConfiguration" enable-management="true" enable-statistics="true"/>
+    </ehcache:service>
+
+    <ehcache:cache-template name="defaultCacheConfiguration">
+        <ehcache:expiry>
+            <ehcache:none/>
+        </ehcache:expiry>
+
+        <ehcache:resources>
+            <!-- The maximal number of entries to be held in the Cache, prior to eviction starting -->
+            <ehcache:heap unit="entries">100000</ehcache:heap>
+        </ehcache:resources>
+    </ehcache:cache-template>
+</ehcache:config>
 
diff --git a/util/src/test/java/org/killbill/billing/util/security/TestPermissionAnnotationMethodInterceptor.java b/util/src/test/java/org/killbill/billing/util/security/TestPermissionAnnotationMethodInterceptor.java
index 566518d..c4a8c52 100644
--- a/util/src/test/java/org/killbill/billing/util/security/TestPermissionAnnotationMethodInterceptor.java
+++ b/util/src/test/java/org/killbill/billing/util/security/TestPermissionAnnotationMethodInterceptor.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
  *
@@ -20,6 +22,13 @@ import javax.inject.Singleton;
 
 import org.apache.shiro.authz.AuthorizationException;
 import org.apache.shiro.authz.UnauthenticatedException;
+import org.killbill.billing.security.Permission;
+import org.killbill.billing.security.RequiresPermissions;
+import org.killbill.billing.tenant.api.TenantInternalApi;
+import org.killbill.billing.util.UtilTestSuiteNoDB;
+import org.killbill.billing.util.dao.NonEntityDao;
+import org.killbill.billing.util.glue.CacheModule;
+import org.killbill.billing.util.glue.KillBillShiroAopModule;
 import org.killbill.billing.util.glue.TestSecurityModuleNoDB;
 import org.killbill.billing.util.glue.TestUtilModuleNoDB.ShiroModuleNoDB;
 import org.mockito.Mockito;
@@ -27,18 +36,10 @@ import org.skife.jdbi.v2.IDBI;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
-import org.killbill.billing.security.Permission;
-import org.killbill.billing.security.RequiresPermissions;
-import org.killbill.billing.util.UtilTestSuiteNoDB;
-import org.killbill.billing.util.glue.KillBillShiroAopModule;
-import org.killbill.billing.util.glue.KillBillShiroModule;
-import org.killbill.billing.util.glue.SecurityModule;
-
 import com.google.inject.AbstractModule;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
-import net.sf.ehcache.CacheManager;
 
 public class TestPermissionAnnotationMethodInterceptor extends UtilTestSuiteNoDB {
 
@@ -73,16 +74,18 @@ public class TestPermissionAnnotationMethodInterceptor extends UtilTestSuiteNoDB
 
         // Now, verify the interception works
         configureShiro();
-        // Shutdown the cache manager to avoid duplicate exceptions
-        CacheManager.getInstance().shutdown();
+
         final Injector injector = Guice.createInjector(Stage.PRODUCTION,
                                                        new ShiroModuleNoDB(configSource),
                                                        new KillBillShiroAopModule(),
                                                        new TestSecurityModuleNoDB(configSource),
+                                                       new CacheModule(configSource),
                                                        new AbstractModule() {
                                                            @Override
                                                            protected void configure() {
                                                                bind(IDBI.class).toInstance(Mockito.mock(IDBI.class));
+                                                               bind(TenantInternalApi.class).toInstance(Mockito.mock(TenantInternalApi.class));
+                                                               bind(NonEntityDao.class).toInstance(Mockito.mock(NonEntityDao.class));
                                                            }
                                                        });
         final AopTester aopedTester = injector.getInstance(AopTester.class);
@@ -101,17 +104,19 @@ public class TestPermissionAnnotationMethodInterceptor extends UtilTestSuiteNoDB
 
         // Now, verify the interception works
         configureShiro();
-        // Shutdown the cache manager to avoid duplicate exceptions
-        CacheManager.getInstance().shutdown();
+
         final Injector injector = Guice.createInjector(Stage.PRODUCTION,
                                                        new ShiroModuleNoDB(configSource),
                                                        new KillBillShiroAopModule(),
                                                        new TestSecurityModuleNoDB(configSource),
+                                                       new CacheModule(configSource),
                                                        new AbstractModule() {
                                                            @Override
                                                            public void configure() {
                                                                bind(IDBI.class).toInstance(Mockito.mock(IDBI.class));
                                                                bind(IAopTester.class).to(AopTesterImpl.class).asEagerSingleton();
+                                                               bind(TenantInternalApi.class).toInstance(Mockito.mock(TenantInternalApi.class));
+                                                               bind(NonEntityDao.class).toInstance(Mockito.mock(NonEntityDao.class));
                                                            }
                                                        });
         final IAopTester aopedTester = injector.getInstance(IAopTester.class);