001package io.ebean; 002 003import io.ebean.config.ContainerConfig; 004import io.ebean.config.DatabaseConfig; 005import io.ebean.service.SpiContainer; 006import io.ebean.service.SpiContainerFactory; 007 008import javax.persistence.PersistenceException; 009import java.util.Iterator; 010import java.util.Properties; 011import java.util.ServiceLoader; 012import java.util.concurrent.locks.ReentrantLock; 013 014/** 015 * Creates Database instances. 016 * <p> 017 * This uses either DatabaseConfig or properties in the application.properties file to 018 * configure and create a Database instance. 019 * <p> 020 * The Database instance can either be registered with the DB singleton or 021 * not. The DB singleton effectively holds a map of Database by a name. 022 * If the Database is registered with the DB singleton you can retrieve it 023 * later via {@link DB#byName(String)}. 024 * <p> 025 * One Database can be nominated as the 'default/primary' Database. Many 026 * methods on the DB singleton such as {@link DB#find(Class)} are just a 027 * convenient way of using the 'default/primary' Database. 028 */ 029public class DatabaseFactory { 030 031 private static final ReentrantLock lock = new ReentrantLock(); 032 private static SpiContainer container; 033 private static String defaultServerName; 034 035 static { 036 EbeanVersion.getVersion(); 037 } 038 039 /** 040 * Initialise the container with clustering configuration. 041 * <p> 042 * Call this prior to creating any Database instances or alternatively set the 043 * ContainerConfig on the DatabaseConfig when creating the first Database instance. 044 */ 045 public static void initialiseContainer(ContainerConfig containerConfig) { 046 lock.lock(); 047 try { 048 container(containerConfig); 049 } finally { 050 lock.unlock(); 051 } 052 } 053 054 /** 055 * Create using properties to configure the database. 056 */ 057 public static Database create(String name) { 058 lock.lock(); 059 try { 060 return container(null).createServer(name); 061 } finally { 062 lock.unlock(); 063 } 064 } 065 066 /** 067 * Create using the DatabaseConfig object to configure the database. 068 * 069 * <pre>{@code 070 * 071 * DatabaseConfig config = new DatabaseConfig(); 072 * config.setName("db"); 073 * config.loadProperties(); 074 * 075 * Database database = DatabaseFactory.create(config); 076 * 077 * }</pre> 078 */ 079 public static Database create(DatabaseConfig config) { 080 lock.lock(); 081 try { 082 if (config.getName() == null) { 083 throw new PersistenceException("The name is null (it is required)"); 084 } 085 Database server = createInternal(config); 086 if (config.isRegister()) { 087 if (config.isDefaultServer()) { 088 if (defaultServerName != null && !defaultServerName.equals(config.getName())) { 089 throw new IllegalStateException("Registering [" + config.getName() + "] as the default server but [" + defaultServerName + "] is already registered as the default"); 090 } 091 defaultServerName = config.getName(); 092 } 093 DbPrimary.setSkip(true); 094 DbContext.getInstance().register(server, config.isDefaultServer()); 095 } 096 return server; 097 } finally { 098 lock.unlock(); 099 } 100 } 101 102 /** 103 * Create using the DatabaseConfig additionally specifying a classLoader to use as the context class loader. 104 */ 105 public static Database createWithContextClassLoader(DatabaseConfig config, ClassLoader classLoader) { 106 lock.lock(); 107 try { 108 ClassLoader currentContextLoader = Thread.currentThread().getContextClassLoader(); 109 Thread.currentThread().setContextClassLoader(classLoader); 110 try { 111 return DatabaseFactory.create(config); 112 } finally { 113 // set the currentContextLoader back 114 Thread.currentThread().setContextClassLoader(currentContextLoader); 115 } 116 } finally { 117 lock.unlock(); 118 } 119 } 120 121 /** 122 * Shutdown gracefully all Database instances cleaning up any resources as required. 123 * <p> 124 * This is typically invoked via JVM shutdown hook and not explicitly called. 125 */ 126 public static void shutdown() { 127 lock.lock(); 128 try { 129 container.shutdown(); 130 } finally { 131 lock.unlock(); 132 } 133 } 134 135 private static Database createInternal(DatabaseConfig config) { 136 return container(config.getContainerConfig()).createServer(config); 137 } 138 139 /** 140 * Return the SpiContainer initialising it if necessary. 141 * 142 * @param containerConfig the configuration controlling clustering communication 143 */ 144 private static SpiContainer container(ContainerConfig containerConfig) { 145 // thread safe in that all calling methods hold lock 146 if (container != null) { 147 return container; 148 } 149 150 if (containerConfig == null) { 151 // effectively load configuration from ebean.properties 152 Properties properties = DbPrimary.getProperties(); 153 containerConfig = new ContainerConfig(); 154 containerConfig.loadFromProperties(properties); 155 } 156 container = createContainer(containerConfig); 157 return container; 158 } 159 160 /** 161 * Create the container instance using the configuration. 162 */ 163 protected static SpiContainer createContainer(ContainerConfig containerConfig) { 164 Iterator<SpiContainerFactory> factories = ServiceLoader.load(SpiContainerFactory.class).iterator(); 165 if (factories.hasNext()) { 166 return factories.next().create(containerConfig); 167 } 168 throw new IllegalStateException("Service loader didn't find a SpiContainerFactory?"); 169 } 170}