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}