All Downloads are FREE. Search and download functionalities are using the official Maven repository.

eu.akkamo.mongo.MongoModule.scala Maven / Gradle / Ivy

package eu.akkamo.mongo

import akka.event.LoggingAdapter
import com.mongodb.ConnectionString
import com.typesafe.config.{Config, ConfigFactory}
import eu.akkamo._
import org.mongodb.scala.{MongoClient, MongoDatabase}

import scala.collection.Map
import scala.util.Try

/**
  * ''Akkamo module'' providing support for ''MongoDB'' database using the official Scala driver
  * (see [[http://mongodb.github.io/mongo-scala-driver/]]).
  *
  * = Example configuration =
  * {{{
  *   akkamo.mongo {
  *
  *     name1 {
  *       aliases = [ "alias1", "alias2" ]
  *       default = true
  *       uri = "mongodb://user:password@localhost:27017/db_name"
  *     }
  *
  *     name2 {
  *       aliases = [ "alias3" ]
  *       uri = "mongodb://user:password@localhost:27017/db_name2"
  *     }
  *   }
  * }}}
  *
  * Above is the working example of simple module configuration. In this configuration example,
  * one ''MongoDB'' connection is created and will be registered into the ''Akkamo'' context.
  * Each configured connection is registered into the ''Akkamo'' context as the
  * [[eu.akkamo.mongo.MongoApi]] interface and is available for injection via its name
  * (e.g. ''name1''), aliases (if defined, e.g. ''alias1'' or ''alias2''), or if defined as
  * ''default'' without any name identifier.
  *
  * For more details about the URI connection string format, see the
  * [[https://docs.mongodb.com/manual/reference/connection-string/ official documentation]].
  *
  * @author Vaclav Svejcar ([email protected])
  */
class MongoModule extends Module with Initializable with Disposable {

  object Keys {
    val Aliases = "aliases"
    val Default = "default"
    val Uri = "uri"
    val ConfigNamespace = "akkamo.mongo"
  }

  override def dependencies(dependencies: Dependency): Dependency = dependencies
    .&&[ConfigModule].&&[LogModule]

  override def initialize(ctx: Context): Res[Context] = Try {
    import eu.akkamo.config.blockAsMap
    val cfg: Config = ctx.get[Config]
    val log: LoggingAdapter = ctx.get[LoggingAdapterFactory].apply(getClass)

    log.info("Initializing 'MongoDB' module...")
    val configMap = blockAsMap(Keys.ConfigNamespace)(cfg).getOrElse(defaultConfig)
    parseConfig(configMap).foldLeft(ctx) { case (context, conn) =>
      log.info(s"Initializing Mongo connection for name '${conn.name}'")
      val mongoApi: MongoApi = createMongoApi(conn)

      // create state builder providing safe way of altering immutable context
      val stateB = new StateBuilder[Context](context)

      // register the MongoApi for the specified connection name
      stateB.next(_.register[MongoApi](mongoApi, Some(conn.name)))

      // register the MongoApi as default (if necessary)
      if (conn.default) stateB.next(_.register[MongoApi](mongoApi))

      // register the MongoApi for all specified connection aliases
      conn.aliases foreach (alias => stateB.next(_.register[MongoApi](mongoApi, Some(alias))))

      stateB.value
    }
  }

  override def dispose(ctx: Context): Res[Unit] = Try {
    val log: LoggingAdapter = ctx.get[LoggingAdapterFactory].apply(getClass)

    ctx.registered[MongoApi] foreach { case (mongoApi, aliases) =>
      log.info(s"Terminating Mongo connection registered for names: ${aliases.mkString(",")}")
      mongoApi.client.close()
    }
  }

  private def createMongoApi(conn: Connection): MongoApi = new MongoApi {
    override def client: MongoClient = MongoClient(conn.uri)

    override def db: MongoDatabase = client.getDatabase(new ConnectionString(conn.uri).getDatabase)
  }

  private def parseConfig(cfg: Map[String, Config]): List[Connection] = {
    import scala.collection.JavaConversions._

    def aliases(conf: Config): Seq[String] =
      if (conf.hasPath(Keys.Aliases)) conf.getStringList(Keys.Aliases) else Seq.empty[String]

    val connections: Iterable[Connection] = cfg map { case (key, value) =>
      val default: Boolean =
        if (cfg.size == 1) true else value.hasPath(Keys.Default) && value.getBoolean(Keys.Default)
      Connection(key, aliases(value), default, value.getString(Keys.Uri))
    }

    val defaultsNo = connections count (_.default)
    if (defaultsNo > 1) throw InitializableError(s"Found $defaultsNo default config blocks in" +
      s"module configuration. Only one module can be declared as default.")

    connections.toList
  }

  private def defaultConfig: Map[String, Config] = Map(
    "default" -> ConfigFactory.parseString("""uri = "mongodb://localhost/default"	""".stripMargin))

  private class StateBuilder[T](initial: T) {
    private var state = initial

    def next(f: T => T): StateBuilder[T] = {
      state = f(state)
      this
    }

    def value: T = state
  }

  private case class Connection(name: String, aliases: Seq[String], default: Boolean, uri: String)

}

/**
  * For each configured ''MongoDB'' connection, instance of this trait is registered into the
  * ''Akkamo context'' and provides API for working with the connection.
  */
trait MongoApi {

  /**
    * Instance of `MongoConnection` for the configured ''MongoDB'' connection (see the
    * [[http://mongodb.github.io/mongo-scala-driver/1.1/scaladoc/#org.mongodb.scala.MongoClient official Scaladoc]]
    * for further details).
    *
    * @return instance of `MongoConnection`
    */
  def client: MongoClient

  /**
    * Instance of `MongoDatabase`, representing the specific ''MongoDB'' database (see the
    * [[http://mongodb.github.io/mongo-scala-driver/1.1/scaladoc/#org.mongodb.scala.MongoDatabase official Scaladoc]]
    * for further details).
    *
    * @return instance of `MongoDatabase`
    */
  def db: MongoDatabase
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy