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