From 485e62b5d6aa48524a264eeb615f779ff4fd262e Mon Sep 17 00:00:00 2001 From: Alexander Winkler Date: Fri, 31 Mar 2017 13:41:08 +0200 Subject: [PATCH] Added multi tenant catalog support. (#987) --- .../java/io/ebean/config/ServerConfig.java | 16 +++ .../ebean/config/TenantCatalogProvider.java | 16 +++ src/main/java/io/ebean/config/TenantMode.java | 5 + .../server/core/InternalConfiguration.java | 2 + .../core/MultiTenantDbCatalogSupplier.java | 125 ++++++++++++++++++ .../EbeanServerFactory_MultiTenancy_Test.java | 37 +++++- 6 files changed, 195 insertions(+), 6 deletions(-) create mode 100644 src/main/java/io/ebean/config/TenantCatalogProvider.java create mode 100644 src/main/java/io/ebeaninternal/server/core/MultiTenantDbCatalogSupplier.java diff --git a/src/main/java/io/ebean/config/ServerConfig.java b/src/main/java/io/ebean/config/ServerConfig.java index 3522131f1..c36612208 100644 --- a/src/main/java/io/ebean/config/ServerConfig.java +++ b/src/main/java/io/ebean/config/ServerConfig.java @@ -129,6 +129,8 @@ public class ServerConfig { private TenantSchemaProvider tenantSchemaProvider; + private TenantCatalogProvider tenantCatalogProvider; + /** * List of interesting classes such as entities, embedded, ScalarTypes, * Listeners, Finders, Controllers etc. @@ -696,6 +698,20 @@ public class ServerConfig { this.tenantSchemaProvider = tenantSchemaProvider; } + /** + * Return the tenancy catalog provider. + */ + public TenantCatalogProvider getTenantCatalogProvider() { + return tenantCatalogProvider; + } + + /** + * Set the tenancy catalog provider. + */ + public void setTenantCatalogProvider(TenantCatalogProvider tenantCatalogProvider) { + this.tenantCatalogProvider = tenantCatalogProvider; + } + /** * Return the PersistBatch mode to use by default at the transaction level. *

diff --git a/src/main/java/io/ebean/config/TenantCatalogProvider.java b/src/main/java/io/ebean/config/TenantCatalogProvider.java new file mode 100644 index 000000000..d8e8d1e0a --- /dev/null +++ b/src/main/java/io/ebean/config/TenantCatalogProvider.java @@ -0,0 +1,16 @@ +package io.ebean.config; + +/** + * For multi-tenancy via DB CATALOG supply the catalog given the tenantId. + */ +@FunctionalInterface +public interface TenantCatalogProvider { + + /** + * Return the DB catalog for the given tenantId. + * + * @param tenantId The current tenant id. + * @return The DB catalog to use for the given tenant + */ + String catalog(Object tenantId); +} diff --git a/src/main/java/io/ebean/config/TenantMode.java b/src/main/java/io/ebean/config/TenantMode.java index 808d93044..1205fca58 100644 --- a/src/main/java/io/ebean/config/TenantMode.java +++ b/src/main/java/io/ebean/config/TenantMode.java @@ -20,6 +20,11 @@ public enum TenantMode { */ SCHEMA(true), + /** + * Each Tenant has their own Database but with in connection pool + */ + CATALOG(true), + /** * Tenants share tables but have a discriminator/partition column that partitions the data. */ diff --git a/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java b/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java index ddad6db12..4f6c3b2f5 100644 --- a/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java +++ b/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java @@ -371,6 +371,8 @@ public class InternalConfiguration { return new MultiTenantDbSupplier(serverConfig.getCurrentTenantProvider(), serverConfig.getTenantDataSourceProvider()); case SCHEMA: return new MultiTenantDbSchemaSupplier(serverConfig.getCurrentTenantProvider(), serverConfig.getDataSource(), serverConfig.getTenantSchemaProvider()); + case CATALOG: + return new MultiTenantDbCatalogSupplier(serverConfig.getCurrentTenantProvider(), serverConfig.getDataSource(), serverConfig.getTenantCatalogProvider()); default: return new SimpleDataSourceProvider(serverConfig.getDataSource()); } diff --git a/src/main/java/io/ebeaninternal/server/core/MultiTenantDbCatalogSupplier.java b/src/main/java/io/ebeaninternal/server/core/MultiTenantDbCatalogSupplier.java new file mode 100644 index 000000000..3caf7dbc3 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/core/MultiTenantDbCatalogSupplier.java @@ -0,0 +1,125 @@ +package io.ebeaninternal.server.core; + +import io.ebean.config.CurrentTenantProvider; +import io.ebean.config.TenantCatalogProvider; +import io.ebeaninternal.server.transaction.DataSourceSupplier; +import org.avaje.datasource.DataSourcePool; + +import javax.sql.DataSource; +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.logging.Logger; + +/** + * DataSource supplier that changes DB catalog based on current Tenant Id. + */ +public class MultiTenantDbCatalogSupplier implements DataSourceSupplier { + + private final CurrentTenantProvider tenantProvider; + + private final DataSource dataSource; + + private final TenantCatalogProvider catalogProvider; + + private final CatalogDataSource catalogDataSource; + + MultiTenantDbCatalogSupplier(CurrentTenantProvider tenantProvider, DataSource dataSource, TenantCatalogProvider catalogProvider) { + this.tenantProvider = tenantProvider; + this.dataSource = dataSource; + this.catalogProvider = catalogProvider; + this.catalogDataSource = new CatalogDataSource(); + } + + @Override + public DataSource getDataSource() { + return catalogDataSource; + } + + @Override + public Connection getConnection(Object tenantId) throws SQLException { + return catalogDataSource.getConnectionForTenant(tenantId); + } + + @Override + public void shutdown(boolean deregisterDriver) { + if (dataSource instanceof DataSourcePool) { + ((DataSourcePool) dataSource).shutdown(deregisterDriver); + } + } + + /** + * Returns the DB catalog for the current user Tenant Id. + */ + private String tenantCatalog() { + return catalogProvider.catalog(tenantProvider.currentId()); + } + + private class CatalogDataSource implements DataSource { + + CatalogDataSource() { + } + + /** + * Return the connection where tenantId is optionally provided by a lazy loading query. + */ + Connection getConnectionForTenant(Object tenantId) throws SQLException { + Connection connection = dataSource.getConnection(); + connection.setCatalog(catalogProvider.catalog(tenantId)); + return connection; + } + + /** + * Return the connection with the appropriate DB catalog set. + */ + @Override + public Connection getConnection() throws SQLException { + + Connection connection = dataSource.getConnection(); + connection.setCatalog(tenantCatalog()); + return connection; + } + + @Override + public Connection getConnection(String username, String password) throws SQLException { + return dataSource.getConnection(username, password); + } + + @Override + public T unwrap(Class iface) throws SQLException { + return dataSource.unwrap(iface); + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return dataSource.isWrapperFor(iface); + } + + @Override + public PrintWriter getLogWriter() throws SQLException { + return dataSource.getLogWriter(); + } + + @Override + public void setLogWriter(PrintWriter out) throws SQLException { + dataSource.setLogWriter(out); + } + + @Override + public void setLoginTimeout(int seconds) throws SQLException { + dataSource.setLoginTimeout(seconds); + } + + @Override + public int getLoginTimeout() throws SQLException { + return dataSource.getLoginTimeout(); + } + + @Override + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + return dataSource.getParentLogger(); + } + + } +} diff --git a/src/test/java/io/ebean/EbeanServerFactory_MultiTenancy_Test.java b/src/test/java/io/ebean/EbeanServerFactory_MultiTenancy_Test.java index 231f2129a..fa6c51821 100644 --- a/src/test/java/io/ebean/EbeanServerFactory_MultiTenancy_Test.java +++ b/src/test/java/io/ebean/EbeanServerFactory_MultiTenancy_Test.java @@ -1,11 +1,6 @@ package io.ebean; -import io.ebean.EbeanServerFactory; -import io.ebean.config.CurrentTenantProvider; -import io.ebean.config.ServerConfig; -import io.ebean.config.TenantDataSourceProvider; -import io.ebean.config.TenantMode; -import io.ebean.config.TenantSchemaProvider; +import io.ebean.config.*; import io.ebean.config.dbplatform.mysql.MySqlPlatform; import io.ebean.config.dbplatform.postgres.PostgresPlatform; import org.junit.Test; @@ -80,4 +75,34 @@ public class EbeanServerFactory_MultiTenancy_Test { EbeanServerFactory.create(config); } + + /** + * Tests using multi tenancy per schema + */ + @Test + public void create_new_server_with_multi_tenancy_catalog() { + + String tenant = "customer"; + CurrentTenantProvider tenantProvider = Mockito.mock(CurrentTenantProvider.class); + Mockito.doReturn(tenant).when(tenantProvider).currentId(); + + TenantCatalogProvider catalogProvider = Mockito.mock(TenantCatalogProvider.class); + Mockito.doReturn("tenant_catalog").when(catalogProvider).catalog(tenant); + + ServerConfig config = new ServerConfig(); + config.setName("h2"); + config.loadFromProperties(); + config.loadTestProperties(); + config.setRegister(false); + config.setDefaultServer(false); + + config.setTenantMode(TenantMode.CATALOG); + config.setCurrentTenantProvider(tenantProvider); + config.setTenantCatalogProvider(catalogProvider); + + config.setDdlRun(false); + config.setDatabasePlatform(new MySqlPlatform()); + + EbeanServerFactory.create(config); + } }