package io.ebeaninternal.server.core;
import io.ebean.config.ContainerConfig;
import io.ebean.config.ServerConfig;
import io.ebean.config.ServerConfigProvider;
import io.ebean.config.TenantMode;
import io.ebean.config.UnderscoreNamingConvention;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.h2.H2Platform;
import io.ebean.datasource.DataSourceAlertFactory;
import io.ebean.datasource.DataSourceConfig;
import io.ebean.datasource.DataSourceFactory;
import io.ebean.datasource.DataSourcePoolListener;
import io.ebean.service.SpiContainer;
import io.ebeaninternal.api.SpiBackgroundExecutor;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.dbmigration.DbOffline;
import io.ebeaninternal.server.cluster.ClusterManager;
import io.ebeaninternal.server.core.bootup.BootupClassPathSearch;
import io.ebeaninternal.server.core.bootup.BootupClasses;
import io.ebeaninternal.server.lib.ShutdownManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.ServiceLoader;
Default Server side implementation of ServerFactory.
/**
* Default Server side implementation of ServerFactory.
*/
public class DefaultContainer implements SpiContainer {
private static final Logger logger = LoggerFactory.getLogger("io.ebean.internal.DefaultContainer");
private final ClusterManager clusterManager;
private final JndiDataSourceLookup jndiDataSourceFactory;
public DefaultContainer(ContainerConfig containerConfig) {
this.clusterManager = new ClusterManager(containerConfig);
this.jndiDataSourceFactory = new JndiDataSourceLookup();
// register so that we can shutdown any Ebean wide
// resources such as clustering
ShutdownManager.registerContainer(this);
}
@Override
public void shutdown() {
clusterManager.shutdown();
ShutdownManager.shutdown();
}
Create the server reading configuration information from ebean.properties.
/**
* Create the server reading configuration information from ebean.properties.
*/
@Override
public SpiEbeanServer createServer(String name) {
ServerConfig config = new ServerConfig();
config.setName(name);
config.loadFromProperties();
return createServer(config);
}
private SpiBackgroundExecutor createBackgroundExecutor(ServerConfig serverConfig) {
String namePrefix = "ebean-" + serverConfig.getName();
int schedulePoolSize = serverConfig.getBackgroundExecutorSchedulePoolSize();
int shutdownSecs = serverConfig.getBackgroundExecutorShutdownSecs();
return new DefaultBackgroundExecutor(schedulePoolSize, shutdownSecs, namePrefix);
}
Create the implementation from the configuration.
/**
* Create the implementation from the configuration.
*/
@Override
public SpiEbeanServer createServer(ServerConfig serverConfig) {
synchronized (this) {
if (serverConfig.isDefaultServer()) {
for (ServerConfigProvider configProvider : ServiceLoader.load(ServerConfigProvider.class)) {
configProvider.apply(serverConfig);
}
}
setNamingConvention(serverConfig);
BootupClasses bootupClasses = getBootupClasses(serverConfig);
boolean online = true;
if (serverConfig.isDocStoreOnly()) {
serverConfig.setDatabasePlatform(new H2Platform());
} else {
TenantMode tenantMode = serverConfig.getTenantMode();
if (TenantMode.DB != tenantMode) {
setDataSource(serverConfig);
if (!tenantMode.isDynamicDataSource()) {
// check the autoCommit and Transaction Isolation
online = checkDataSource(serverConfig);
}
}
}
// determine database platform (Oracle etc)
setDatabasePlatform(serverConfig);
if (serverConfig.getDbEncrypt() != null) {
// use a configured DbEncrypt rather than the platform default
serverConfig.getDatabasePlatform().setDbEncrypt(serverConfig.getDbEncrypt());
}
// inform the NamingConvention of the associated DatabasePlatform
serverConfig.getNamingConvention().setDatabasePlatform(serverConfig.getDatabasePlatform());
// executor and l2 caching service setup early (used during server construction)
SpiBackgroundExecutor executor = createBackgroundExecutor(serverConfig);
InternalConfiguration c = new InternalConfiguration(online, clusterManager, executor, serverConfig, bootupClasses);
DefaultServer server = new DefaultServer(c, c.cacheManager());
// generate and run DDL if required
// if there are any other tasks requiring action in their plugins, do them as well
if (!DbOffline.isGenerateMigration()) {
server.executePlugins(online);
// initialise prior to registering with clusterManager
server.initialise();
if (online) {
if (clusterManager.isClustering()) {
// register the server once it has been created
clusterManager.registerServer(server);
}
}
// start any services after registering with clusterManager
server.start();
}
DbOffline.reset();
return server;
}
}
Get the entities, scalarTypes, Listeners etc combining the class registered
ones with the already created instances.
/**
* Get the entities, scalarTypes, Listeners etc combining the class registered
* ones with the already created instances.
*/
private BootupClasses getBootupClasses(ServerConfig serverConfig) {
BootupClasses bootup = getBootupClasses1(serverConfig);
bootup.addIdGenerators(serverConfig.getIdGenerators());
bootup.addPersistControllers(serverConfig.getPersistControllers());
bootup.addPostLoaders(serverConfig.getPostLoaders());
bootup.addPostConstructListeners(serverConfig.getPostConstructListeners());
bootup.addFindControllers(serverConfig.getFindControllers());
bootup.addPersistListeners(serverConfig.getPersistListeners());
bootup.addQueryAdapters(serverConfig.getQueryAdapters());
bootup.addServerConfigStartup(serverConfig.getServerConfigStartupListeners());
bootup.addChangeLogInstances(serverConfig);
// run any ServerConfigStartup instances
bootup.runServerConfigStartup(serverConfig);
return bootup;
}
Get the class based entities, scalarTypes, Listeners etc.
/**
* Get the class based entities, scalarTypes, Listeners etc.
*/
private BootupClasses getBootupClasses1(ServerConfig serverConfig) {
List<Class<?>> entityClasses = serverConfig.getClasses();
if (serverConfig.isDisableClasspathSearch() || (entityClasses != null && !entityClasses.isEmpty())) {
// use classes we explicitly added via configuration
return new BootupClasses(entityClasses);
}
return BootupClassPathSearch.search(serverConfig);
}
Set the naming convention to underscore if it has not already been set.
/**
* Set the naming convention to underscore if it has not already been set.
*/
private void setNamingConvention(ServerConfig config) {
if (config.getNamingConvention() == null) {
config.setNamingConvention(new UnderscoreNamingConvention());
}
}
Set the DatabasePlatform if it has not already been set.
/**
* Set the DatabasePlatform if it has not already been set.
*/
private void setDatabasePlatform(ServerConfig config) {
DatabasePlatform platform = config.getDatabasePlatform();
if (platform == null) {
if (config.getTenantMode().isDynamicDataSource()) {
throw new IllegalStateException("DatabasePlatform must be explicitly set on ServerConfig for TenantMode "+config.getTenantMode());
}
// automatically determine the platform
platform = new DatabasePlatformFactory().create(config);
config.setDatabasePlatform(platform);
}
logger.info("DatabasePlatform name:{} platform:{}", config.getName(), platform.getName());
platform.configure(config.getPlatformConfig());
}
Set the DataSource if it has not already been set.
/**
* Set the DataSource if it has not already been set.
*/
private void setDataSource(ServerConfig config) {
if (config.getDataSource() == null) {
config.setDataSource(getDataSourceFromConfig(config, false));
}
if (config.getReadOnlyDataSource() == null && config.isAutoReadOnlyDataSource()) {
config.setReadOnlyDataSource(getDataSourceFromConfig(config, true));
}
}
private DataSource getDataSourceFromConfig(ServerConfig config, boolean readOnly) {
if (isOfflineMode(config)) {
logger.debug("... DbOffline using platform [{}]", DbOffline.getPlatform());
return null;
}
if (!readOnly && config.getDataSourceJndiName() != null) {
DataSource ds = jndiDataSourceFactory.lookup(config.getDataSourceJndiName());
if (ds == null) {
throw new PersistenceException("JNDI lookup for DataSource " + config.getDataSourceJndiName() + " returned null.");
} else {
return ds;
}
}
DataSourceConfig dsConfig = (readOnly) ? config.getReadOnlyDataSourceConfig() : config.getDataSourceConfig();
if (dsConfig == null) {
throw new PersistenceException("No DataSourceConfig defined for " + config.getName());
}
if (dsConfig.isOffline()) {
if (config.getDatabasePlatformName() == null) {
throw new PersistenceException("You MUST specify a DatabasePlatformName on ServerConfig when offline");
}
return null;
}
DataSourceFactory factory = DataSourceFactory.get();
if (factory == null) {
throw new IllegalStateException("No DataSourceFactory service implementation found in class path."
+ " Probably missing dependency to avaje-datasource?");
}
DataSourceAlertFactory alertFactory = config.service(DataSourceAlertFactory.class);
if (alertFactory != null) {
dsConfig.setAlert(alertFactory.createAlert());
}
attachListener(config, dsConfig);
if (readOnly) {
// setup to use AutoCommit such that we skip explicit commit
dsConfig.setAutoCommit(true);
//dsConfig.setReadOnly(true);
dsConfig.setDefaults(config.getDataSourceConfig());
dsConfig.setIsolationLevel(config.getDataSourceConfig().getIsolationLevel());
}
String poolName = config.getName() + (readOnly ? "-ro" : "");
return factory.createPool(poolName, dsConfig);
}
Create and attach a DataSourcePoolListener if it has been specified via properties and there is not one already attached.
/**
* Create and attach a DataSourcePoolListener if it has been specified via properties and there is not one already attached.
*/
private void attachListener(ServerConfig config, DataSourceConfig dsConfig) {
if (dsConfig.getListener() == null) {
String poolListener = dsConfig.getPoolListener();
if (poolListener != null) {
dsConfig.setListener((DataSourcePoolListener) config.getClassLoadConfig().newInstance(poolListener));
}
}
}
private boolean isOfflineMode(ServerConfig serverConfig) {
return serverConfig.isDbOffline() || DbOffline.isSet();
}
Check the autoCommit and Transaction Isolation levels of the DataSource.
If autoCommit is true this could be a real problem.
If the Isolation level is not READ_COMMITTED then optimistic concurrency
checking may not work as expected.
/**
* Check the autoCommit and Transaction Isolation levels of the DataSource.
* <p>
* If autoCommit is true this could be a real problem.
* </p>
* <p>
* If the Isolation level is not READ_COMMITTED then optimistic concurrency
* checking may not work as expected.
* </p>
*/
private boolean checkDataSource(ServerConfig serverConfig) {
if (isOfflineMode(serverConfig)) {
return false;
}
if (serverConfig.getDataSource() == null) {
if (serverConfig.getDataSourceConfig().isOffline()) {
// this is ok - offline DDL generation etc
return false;
}
throw new RuntimeException("DataSource not set?");
}
try (Connection connection = serverConfig.getDataSource().getConnection()) {
if (!serverConfig.isAutoCommitMode() && connection.getAutoCommit()) {
logger.warn("DataSource [{}] has autoCommit defaulting to true!", serverConfig.getName());
}
return true;
} catch (SQLException ex) {
throw new PersistenceException(ex);
}
}
}