Skip to content

Commit

Permalink
Merge pull request #2274 from hongwei1/custom-db-vendor
Browse files Browse the repository at this point in the history
feature/AddedCustomDbVendor
  • Loading branch information
simonredfern authored Sep 18, 2023
2 parents 2b509fe + aeb995b commit 86ed812
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 6 deletions.
6 changes: 3 additions & 3 deletions obp-api/src/main/scala/bootstrap/liftweb/Boot.scala
Original file line number Diff line number Diff line change
Expand Up @@ -242,18 +242,18 @@ class Boot extends MdcLoggable {
val vendor =
Props.mode match {
case Props.RunModes.Production | Props.RunModes.Staging | Props.RunModes.Development =>
new StandardDBVendor(driver,
new CustomDBVendor(driver,
APIUtil.getPropsValue("db.url") openOr "jdbc:h2:lift_proto.db;AUTO_SERVER=TRUE",
APIUtil.getPropsValue("db.user"), APIUtil.getPropsValue("db.password"))
case Props.RunModes.Test =>
new StandardDBVendor(
new CustomDBVendor(
driver,
APIUtil.getPropsValue("db.url") openOr Constant.h2DatabaseDefaultUrlValue,
APIUtil.getPropsValue("db.user").orElse(Empty),
APIUtil.getPropsValue("db.password").orElse(Empty)
)
case _ =>
new StandardDBVendor(
new CustomDBVendor(
driver,
h2DatabaseDefaultUrlValue,
Empty, Empty)
Expand Down
149 changes: 149 additions & 0 deletions obp-api/src/main/scala/bootstrap/liftweb/CustomDBVendor.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package bootstrap.liftweb

import java.sql.{Connection, DriverManager}

import net.liftweb.common.{Box, Failure, Full, Logger}
import net.liftweb.db.ConnectionManager
import net.liftweb.util.ConnectionIdentifier
import net.liftweb.util.Helpers.tryo

/**
* The standard DB vendor.
*
* @param driverName the name of the database driver
* @param dbUrl the URL for the JDBC data connection
* @param dbUser the optional username
* @param dbPassword the optional db password
*/
class CustomDBVendor(driverName: String,
dbUrl: String,
dbUser: Box[String],
dbPassword: Box[String]) extends CustomProtoDBVendor {

private val logger = Logger(classOf[CustomDBVendor])

protected def createOne: Box[Connection] = {
tryo{t:Throwable => logger.error("Cannot load database driver: %s".format(driverName), t)}{Class.forName(driverName);()}

(dbUser, dbPassword) match {
case (Full(user), Full(pwd)) =>
tryo{t:Throwable => logger.error("Unable to get database connection. url=%s, user=%s".format(dbUrl, user),t)}(DriverManager.getConnection(dbUrl, user, pwd))
case _ =>
tryo{t:Throwable => logger.error("Unable to get database connection. url=%s".format(dbUrl),t)}(DriverManager.getConnection(dbUrl))
}
}
}

trait CustomProtoDBVendor extends ConnectionManager {
private val logger = Logger(classOf[CustomProtoDBVendor])
private var pool: List[Connection] = Nil
private var poolSize = 0
private var tempMaxSize = maxPoolSize

/**
* Override and set to false if the maximum pool size can temporarily be expanded to avoid pool starvation
*/
protected def allowTemporaryPoolExpansion = true

/**
* Override this method if you want something other than
* 4 connections in the pool
*/
protected def maxPoolSize = 4

/**
* The absolute maximum that this pool can extend to
* The default is 20. Override this method to change.
*/
protected def doNotExpandBeyond = 20

/**
* The logic for whether we can expand the pool beyond the current size. By
* default, the logic tests allowTemporaryPoolExpansion && poolSize <= doNotExpandBeyond
*/
protected def canExpand_? : Boolean = allowTemporaryPoolExpansion && poolSize <= doNotExpandBeyond

/**
* How is a connection created?
*/
protected def createOne: Box[Connection]

/**
* Test the connection. By default, setAutoCommit(false),
* but you can do a real query on your RDBMS to see if the connection is alive
*/
protected def testConnection(conn: Connection) {
conn.setAutoCommit(false)
}

def newConnection(name: ConnectionIdentifier): Box[Connection] =
synchronized {
pool match {
case Nil if poolSize < tempMaxSize =>
val ret = createOne
ret.foreach(_.setAutoCommit(false))
poolSize = poolSize + 1
logger.debug("Created new pool entry. name=%s, poolSize=%d".format(name, poolSize))
ret

case Nil =>
val curSize = poolSize
logger.trace("No connection left in pool, waiting...")
wait(50L*poolSize )
// if we've waited 50 ms and the pool is still empty, temporarily expand it
if (pool.isEmpty && poolSize == curSize && canExpand_?) {
tempMaxSize += 1
logger.debug("Temporarily expanding pool. name=%s, tempMaxSize=%d".format(name, tempMaxSize))
newConnection(name)
}else{
logger.debug(s"The poolSize is expanding to tempMaxSize ($tempMaxSize), we can not create new connection, need to restart OBP now.")
throw new RuntimeException(s"Database may be down, please check database connection! OBP already create $tempMaxSize connections, because all connections are occupied!")
}

case x :: xs =>
logger.trace("Found connection in pool, name=%s".format(name))
pool = xs
try {
this.testConnection(x)
Full(x)
} catch {
case e: Exception => try {
logger.debug("Test connection failed, removing connection from pool, name=%s".format(name))
poolSize = poolSize - 1
tryo(x.close)
newConnection(name)
} catch {
case e: Exception => newConnection(name)
}
}
}
}

def releaseConnection(conn: Connection): Unit = synchronized {
if (tempMaxSize > maxPoolSize) {
tryo {conn.close()}
tempMaxSize -= 1
poolSize -= 1
} else {
pool = conn :: pool
}
logger.debug("Released connection. poolSize=%d".format(poolSize))
notifyAll
}

def closeAllConnections_!(): Unit = _closeAllConnections_!(0)


private def _closeAllConnections_!(cnt: Int): Unit = synchronized {
logger.info("Closing all connections")
if (poolSize <= 0 || cnt > 10) ()
else {
pool.foreach {c => tryo(c.close); poolSize -= 1}
pool = Nil

if (poolSize > 0) wait(250)

_closeAllConnections_!(cnt + 1)
}
}
}
6 changes: 3 additions & 3 deletions obp-api/src/main/scala/code/remotedata/RemotedataActors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import java.util.concurrent.TimeUnit

import akka.actor.{ActorSystem, Props => ActorProps}
import bootstrap.liftweb.ToSchemify
import bootstrap.liftweb.CustomDBVendor
import code.actorsystem.{ObpActorConfig, ObpLookupSystem}
import code.api.util.APIUtil
import code.util.Helper
import code.util.Helper.MdcLoggable
import com.typesafe.config.ConfigFactory
import net.liftweb.common._
import net.liftweb.db.StandardDBVendor
import net.liftweb.http.LiftRules
import net.liftweb.mapper.{DB, Schemifier}
import net.liftweb.util.Props
Expand Down Expand Up @@ -94,11 +94,11 @@ object RemotedataActors extends MdcLoggable {
val vendor =
Props.mode match {
case Props.RunModes.Production | Props.RunModes.Staging | Props.RunModes.Development =>
new StandardDBVendor(driver,
new CustomDBVendor(driver,
APIUtil.getPropsValue("remotedata.db.url") openOr "jdbc:h2:./lift_proto.remotedata.db;AUTO_SERVER=TRUE",
APIUtil.getPropsValue("remotedata.db.user"), APIUtil.getPropsValue("remotedata.db.password"))
case _ =>
new StandardDBVendor(
new CustomDBVendor(
driver,
"jdbc:h2:mem:OBPData;DB_CLOSE_DELAY=-1",
Empty, Empty)
Expand Down

0 comments on commit 86ed812

Please sign in to comment.