package play.api.db
import java.sql.{ Connection, Driver, DriverManager }
import javax.sql.DataSource
import play.utils.{ ProxyDriver, Reflect }
import com.typesafe.config.Config
import play.api.inject.{ NewInstanceInjector, Injector }
import scala.util.control.{ NonFatal, ControlThrowable }
import play.api.{ Environment, Configuration, Logger, PlayConfig }
object Databases {
def apply(driver: String, url: String, name: String = "default", config: Map[String, _ <: Any] = Map.empty): Database = {
val dbConfig = Configuration.reference.getConfig("play.db.prototype").get ++
Configuration.from(Map("driver" -> driver, "url" -> url) ++ config)
new PooledDatabase(name, dbConfig)
}
def inMemory(name: String = "default", urlOptions: Map[String, String] = Map.empty, config: Map[String, _ <: Any] = Map.empty): Database = {
val driver = "org.h2.Driver"
val = urlOptions.map { case (, ) => k + "=" + v }.mkString(";", ";", "")
val url = "jdbc:h2:mem:" + name + urlExtra
Databases(driver, url, name, config)
}
def withDatabase[T](driver: String, url: String, name: String = "default",
config: Map[String, _ <: Any] = Map.empty)(block: Database => T): T = {
val database = Databases(driver, url, name, config)
try {
block(database)
} finally {
database.shutdown()
}
}
def withInMemory[T](name: String = "default", urlOptions: Map[String, String] = Map.empty,
config: Map[String, _ <: Any] = Map.empty)(block: Database => T): T = {
val database = inMemory(name, urlOptions, config)
try {
block(database)
} finally {
database.shutdown()
}
}
}
abstract class DefaultDatabase(val name: String, configuration: Config, environment: Environment) extends Database {
private val config = PlayConfig(configuration)
val databaseConfig = DatabaseConfig.fromConfig(config, environment)
def createDataSource(): DataSource
def closeDataSource(dataSource: DataSource): Unit
lazy val driver: Option[Driver] = {
databaseConfig.driver.map { driverClassName =>
try {
val proxyDriver = new ProxyDriver(Reflect.createInstance[Driver](driverClassName, environment.classLoader))
DriverManager.registerDriver(proxyDriver)
proxyDriver
} catch {
case NonFatal(e) => throw config.reportError("driver", s"Driver not found: [$driverClassName}]", Some(e))
}
}
}
lazy val dataSource: DataSource = {
driver
createDataSource
}
lazy val url: String = {
val connection = dataSource.getConnection
try {
connection.getMetaData.getURL
} finally {
connection.close()
}
}
def getConnection(): Connection = {
getConnection(autocommit = true)
}
def getConnection(autocommit: Boolean): Connection = {
val connection = dataSource.getConnection
connection.setAutoCommit(autocommit)
connection
}
def withConnection[A](block: Connection => A): A = {
withConnection(autocommit = true)(block)
}
def withConnection[A](autocommit: Boolean)(block: Connection => A): A = {
val connection = getConnection(autocommit)
try {
block(connection)
} finally {
connection.close()
}
}
def withTransaction[A](block: Connection => A): A = {
withConnection(autocommit = false) { connection =>
try {
val r = block(connection)
connection.commit()
r
} catch {
case e: ControlThrowable =>
connection.commit()
throw e
case e: Throwable =>
connection.rollback()
throw e
}
}
}
def shutdown(): Unit = {
closeDataSource(dataSource)
deregisterDriver()
}
def deregisterDriver(): Unit = {
driver.foreach(DriverManager.deregisterDriver)
}
}
class PooledDatabase(name: String, configuration: Config, environment: Environment, pool: ConnectionPool)
extends DefaultDatabase(name, configuration, environment) {
def this(name: String, configuration: Configuration) = this(name, configuration.underlying, Environment.simple(), new HikariCPConnectionPool(Environment.simple()))
def createDataSource(): DataSource = pool.create(name, databaseConfig, configuration)
def closeDataSource(dataSource: DataSource): Unit = pool.close(dataSource)
}