From 88c6ec358c86d33ba1573515f9b37c6855bd26ef Mon Sep 17 00:00:00 2001 From: rob bygrave Date: Sat, 20 May 2017 13:53:46 +1200 Subject: [PATCH] #981 Additional refactor to 981 - use composed key in multi tenant environment - Expose TenantAwareKey handling for plugins --- pom.xml | 2 +- .../java/io/ebean/cache/TenantAwareKey.java | 77 +++++++++++++++++ .../server/cache/DefaultServerCache.java | 83 ++++--------------- .../java/io/ebean/cache/CacheKeyTest.java | 72 ++++++++++++++++ 4 files changed, 168 insertions(+), 66 deletions(-) create mode 100644 src/main/java/io/ebean/cache/TenantAwareKey.java create mode 100644 src/test/java/io/ebean/cache/CacheKeyTest.java diff --git a/pom.xml b/pom.xml index c52f8e332..0a218f01b 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ io.ebean ebean - 10.2.4-SNAPSHOT + 10.3.1-SNAPSHOT jar ebean diff --git a/src/main/java/io/ebean/cache/TenantAwareKey.java b/src/main/java/io/ebean/cache/TenantAwareKey.java new file mode 100644 index 000000000..96ebcbc0e --- /dev/null +++ b/src/main/java/io/ebean/cache/TenantAwareKey.java @@ -0,0 +1,77 @@ +package io.ebean.cache; + +import io.ebean.config.CurrentTenantProvider; + +import java.io.Serializable; +import java.util.Objects; + +/** + * Tenant aware handling for caching. + */ +public class TenantAwareKey { + + private final CurrentTenantProvider tenantProvider; + + /** + * Construct with a tenant provider than can be null. + */ + public TenantAwareKey(CurrentTenantProvider tenantProvider) { + this.tenantProvider = tenantProvider; + } + + /** + * Return a tenant aware key. + */ + public Object key(Object key) { + if (tenantProvider != null) { + return new CacheKey(key, tenantProvider.currentId()); + } else { + return key; + } + } + + /** + * We use a combined key, if this serverCache is per tenant. + */ + public static final class CacheKey implements Serializable { + + private static final long serialVersionUID = 1L; + + final Object key; + final Object tenantId; + + /** + * Create with optional tenantId. + */ + public CacheKey(Object key, Object tenantId) { + this.key = key; + this.tenantId = tenantId; + } + + @Override + public int hashCode() { + int result = key.hashCode(); + result = 92821 * result + Objects.hashCode(tenantId); + return result; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof CacheKey) { + CacheKey that = (CacheKey) obj; + return Objects.equals(that.key, this.key) + && Objects.equals(that.tenantId, this.tenantId); + } + return false; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(key.toString()); + if (tenantId != null) { + sb.append(":").append(tenantId); + } + return sb.toString(); + } + } +} diff --git a/src/main/java/io/ebeaninternal/server/cache/DefaultServerCache.java b/src/main/java/io/ebeaninternal/server/cache/DefaultServerCache.java index f9187db0c..060e27701 100644 --- a/src/main/java/io/ebeaninternal/server/cache/DefaultServerCache.java +++ b/src/main/java/io/ebeaninternal/server/cache/DefaultServerCache.java @@ -4,6 +4,7 @@ import io.ebean.BackgroundExecutor; import io.ebean.cache.ServerCache; import io.ebean.cache.ServerCacheOptions; import io.ebean.cache.ServerCacheStatistics; +import io.ebean.cache.TenantAwareKey; import io.ebean.config.CurrentTenantProvider; import org.slf4j.Logger; @@ -35,44 +36,6 @@ public class DefaultServerCache implements ServerCache { */ public static final CompareByLastAccess BY_LAST_ACCESS = new CompareByLastAccess(); -/** - * We use a combined key, if this serverCache is per tenant. - */ - public static final class CacheKey implements Serializable { - private static final long serialVersionUID = 1L; - - final Object tenantId; - final Object key; - - CacheKey(Object tenantId, Object key) { - super(); - this.tenantId = tenantId; - this.key = key; - } - - @Override - public int hashCode() { - int result = key.hashCode(); - result = 31 * result + ((tenantId == null) ? 0 : tenantId.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof CacheKey) { - CacheKey other = (CacheKey) obj; - if (other.key.equals(this.key)) { - if (other.tenantId == null && this.tenantId == null) { - return true; - } else if (this.tenantId != null) { - return this.tenantId.equals(other.tenantId); - } - } - } - return false; - } - } - /** * The underlying map (ConcurrentHashMap or similar) */ @@ -91,13 +54,6 @@ public class DefaultServerCache implements ServerCache { protected final LongAdder evictCount = new LongAdder(); protected final LongAdder evictMicros = new LongAdder(); - /** - * if tenantContext is available, use it to generate cacheKey. - */ - protected final CurrentTenantProvider tenantProvider; - - protected final Object monitor = new Object(); - protected final String name; protected int maxSize; @@ -108,6 +64,8 @@ public class DefaultServerCache implements ServerCache { protected int maxSecsToLive; + protected TenantAwareKey tenantAwareKey; + /** * Construct using a ConcurrentHashMap and cache options. */ @@ -129,7 +87,7 @@ public class DefaultServerCache implements ServerCache { this.name = name; this.map = map; this.maxSize = maxSize; - this.tenantProvider = tenantProvider; + this.tenantAwareKey = new TenantAwareKey(tenantProvider); this.maxIdleSecs = maxIdleSecs; this.maxSecsToLive = maxSecsToLive; this.trimFrequency = determineTrim(maxIdleSecs, maxSecsToLive, trimFrequency); @@ -233,22 +191,20 @@ public class DefaultServerCache implements ServerCache { map.clear(); } - private Object convertKey(Object key) { - if (tenantProvider != null) { - return new CacheKey(tenantProvider.currentId(), key); - } else { - return key; - } + /** + * Return the tenant aware key. + */ + private Object key(Object id) { + return tenantAwareKey.key(id); } + /** * Return a value from the cache. */ @Override - public Object get(Object key) { + public Object get(Object id) { - key = convertKey(key); - - CacheEntry entry = map.get(key); + CacheEntry entry = map.get(key(id)); if (entry == null) { missCount.increment(); return null; @@ -265,10 +221,9 @@ public class DefaultServerCache implements ServerCache { * Put a value into the cache. */ @Override - public Object put(Object key, Object value) { - - key = convertKey(key); - + public Object put(Object id, Object value) { + + Object key = key(id); CacheEntry entry = map.put(key, new CacheEntry(key, value)); if (entry == null) { insertCount.increment(); @@ -283,11 +238,9 @@ public class DefaultServerCache implements ServerCache { * Remove an entry from the cache. */ @Override - public Object remove(Object key) { - - key = convertKey(key); - - CacheEntry entry = map.remove(key); + public Object remove(Object id) { + + CacheEntry entry = map.remove(key(id)); if (entry == null) { return null; } else { diff --git a/src/test/java/io/ebean/cache/CacheKeyTest.java b/src/test/java/io/ebean/cache/CacheKeyTest.java new file mode 100644 index 000000000..98225a6e6 --- /dev/null +++ b/src/test/java/io/ebean/cache/CacheKeyTest.java @@ -0,0 +1,72 @@ +package io.ebean.cache; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CacheKeyTest { + + @Test + public void equals_when_null_tenantId() { + + TenantAwareKey.CacheKey key0 = key("12", null); + TenantAwareKey.CacheKey key1 = key("12", null); + + assertMatchEquals(key0, key1); + } + + private void assertMatchEquals(TenantAwareKey.CacheKey key0, TenantAwareKey.CacheKey key1) { + assertThat(key0.hashCode()).isEqualTo(key1.hashCode()); + assertThat(key0.equals(key1)).isTrue(); + assertThat(key0.toString()).isEqualTo(key1.toString()); + } + + @Test + public void equals_when_same_tenantId() { + + TenantAwareKey.CacheKey key0 = key(42L, "1"); + TenantAwareKey.CacheKey key1 = key(42L, "1"); + + assertMatchEquals(key0, key1); + } + + @Test + public void not_equal_when_diff_both() { + + TenantAwareKey.CacheKey key0 = key(42L, "1"); + TenantAwareKey.CacheKey key1 = key(43L, "2"); + + assertThat(key0.equals(key1)).isFalse(); + } + + @Test + public void not_equal_when_diff_key() { + + TenantAwareKey.CacheKey key0 = key(42L, "1"); + TenantAwareKey.CacheKey key1 = key(43L, "1"); + + assertThat(key0.equals(key1)).isFalse(); + } + + @Test + public void not_equal_when_diff_key_andNoTenantId() { + + TenantAwareKey.CacheKey key0 = key(42L, null); + TenantAwareKey.CacheKey key1 = key(43L, null); + + assertThat(key0.equals(key1)).isFalse(); + } + + @Test + public void not_equal_when_diff_tenantId() { + + TenantAwareKey.CacheKey key0 = key(42L, "1"); + TenantAwareKey.CacheKey key1 = key(42L, "2"); + + assertThat(key0.equals(key1)).isFalse(); + } + + private TenantAwareKey.CacheKey key(Object key, Object tenantId) { + return new TenantAwareKey.CacheKey(key, tenantId); + } +}