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