MongoDBService.scala 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. package com.weEat.services
  2. import play.api.Configuration
  3. import org.mongodb.scala.{MongoClient,MongoDatabase,MongoCollection}
  4. import org.mongodb.scala.bson.codecs.Macros._
  5. import org.mongodb.scala.bson.codecs._
  6. import org.bson.{BsonReader,BsonWriter}
  7. import org.bson.codecs.{Codec,DecoderContext,EncoderContext}
  8. import org.bson.codecs.configuration.{CodecProvider,CodecRegistry}
  9. import org.bson.codecs.configuration.CodecRegistries._
  10. import com.weEat.models._
  11. import com.weEat.shared.models.{FoodNodeId,USDANodeId,Ingredient,UnitType,MeasureUnit,RecipeNodeId}
  12. import Ingredient._
  13. import javax.inject.{Inject,Singleton}
  14. import scala.reflect.ClassTag
  15. import com.weEat.migrations.{Migration,Metadata}
  16. import scala.concurrent.ExecutionContext
  17. import scala.util.{Try,Success,Failure}
  18. import gov.usda.nal.fdc.models.Nutrient
  19. @Singleton
  20. class MongoDBService @Inject()(config: Configuration) {
  21. implicit val ec = scala.concurrent.ExecutionContext.global
  22. private val prefix = "mongo"
  23. val url = config.getOptional[String](s"$prefix.url").getOrElse("localhost")
  24. val name = config.getOptional[String](s"$prefix.name").getOrElse("recipes")
  25. val user = config.getOptional[String](s"$prefix.user").getOrElse("application")
  26. val ssl = config.getOptional[Boolean](s"$prefix.ssl").getOrElse(true)
  27. val port = config.getOptional[Int](s"$prefix.port").getOrElse(27017)
  28. val codecRegistry = fromRegistries(
  29. fromProviders(
  30. WrapperCodecProvider(classOf[FoodNodeId],
  31. { n: USDANodeId =>
  32. n.copy(nutrients = n.nutrients.map {
  33. case (k, v) => (k.replace(".", "$"), v)
  34. })
  35. }, { n: USDANodeId =>
  36. n.copy(nutrients = n.nutrients.map {
  37. case (k, v) => (k.replace("$", "."), v)
  38. })
  39. }),
  40. classOf[Metadata],
  41. classOf[IngredientId],
  42. classOf[Ingredient],
  43. classOf[FoodNodeId],
  44. classOf[User],
  45. classOf[Authorization],
  46. classOf[Nutrient],
  47. UnitTypeEnumCodecProvider,
  48. MeasureUnitCodecProvider
  49. ),
  50. MongoClient.DEFAULT_CODEC_REGISTRY
  51. )
  52. val con = {
  53. import org.mongodb.scala._
  54. import org.mongodb.scala.connection.{SslSettings,ClusterSettings}
  55. import scala.jdk.CollectionConverters._
  56. implicit val ec: ExecutionContext = ExecutionContext.global
  57. val password = Option(config.get[String](s"$prefix.password"))
  58. .getOrElse("").toCharArray()
  59. val credential = MongoCredential.createCredential(user, name, password)
  60. val db = MongoClient(
  61. MongoClientSettings.builder()
  62. .applyToSslSettings((builder: SslSettings.Builder) =>
  63. builder.enabled(ssl)
  64. ).applyToClusterSettings((builder: ClusterSettings.Builder) =>
  65. builder.hosts(List(new ServerAddress(url, port)).asJava)
  66. ).credential(credential)
  67. .build()
  68. ).getDatabase(name)
  69. .withCodecRegistry(codecRegistry)
  70. Migration.updateToLatest(db).map(_ => db)
  71. }
  72. def apply[T](coll: Collectable[T])(implicit ct: ClassTag[T]) =
  73. con.map(_.getCollection[T](coll.collectionName))
  74. def withCollection[T, U](coll: Collectable[U])(fn: (MongoCollection[U] => T))
  75. (implicit ct: ClassTag[U]) =
  76. apply(coll).transform({
  77. case Success(collection) => Success(fn(collection))
  78. case Failure(e) => Failure(e)
  79. })
  80. }
  81. case class WrapperCodecProvider[T](
  82. provider: CodecProvider,
  83. encodeFn: (T => T),
  84. decodeFn: (T => T)
  85. )(implicit m: ClassTag[T]) extends CodecProvider {
  86. override def get[U](c: Class[U], registry: CodecRegistry): Codec[U] =
  87. if (m.runtimeClass.isAssignableFrom(c) && c.isAssignableFrom(m.runtimeClass))
  88. WrapperCodec(provider.get(c, registry))(
  89. encodeFn.asInstanceOf[U => U],
  90. decodeFn.asInstanceOf[U => U]
  91. ).asInstanceOf[Codec[U]]
  92. else null
  93. }
  94. case class WrapperCodec[T](val inner: Codec[T])(
  95. encodeFn: (T => T),
  96. decodeFn: (T => T)
  97. ) extends Codec[T] {
  98. override def decode(
  99. reader: BsonReader,
  100. decoderContext: DecoderContext
  101. ): T = decodeFn(inner.decode(reader, decoderContext))
  102. override def encode(
  103. writer: BsonWriter,
  104. value: T,
  105. encoderContext: EncoderContext
  106. ): Unit = inner.encode(writer, encodeFn(value), encoderContext)
  107. override def getEncoderClass: Class[T] = inner.getEncoderClass
  108. }
  109. object UnitTypeEnumCodecProvider extends CodecProvider {
  110. def isCaseObjectEnum[T](clazz: Class[T]): Boolean =
  111. clazz.isInstance(UnitType.MASS) ||
  112. clazz.isInstance(UnitType.VOLUME) ||
  113. clazz.isInstance(UnitType.NUMBER)
  114. override def get[T](clazz: Class[T], registry: CodecRegistry): Codec[T] =
  115. if (isCaseObjectEnum(clazz)) UnitTypeEnumCodec.asInstanceOf[Codec[T]]
  116. else null
  117. object UnitTypeEnumCodec extends Codec[UnitType.UnitType] {
  118. override def decode(
  119. reader: BsonReader,
  120. decoderContext: DecoderContext
  121. ): UnitType.UnitType = UnitType.withName(reader.readString())
  122. override def encode(
  123. writer: BsonWriter,
  124. value: UnitType.UnitType,
  125. encoderContext: EncoderContext
  126. ): Unit = writer.writeString(value.name)
  127. override def getEncoderClass: Class[UnitType.UnitType] =
  128. UnitType.getClass.asInstanceOf[Class[UnitType.UnitType]]
  129. }
  130. }
  131. object MeasureUnitCodecProvider extends CodecProvider {
  132. def isCaseObjectEnum[T](clazz: Class[T]): Boolean =
  133. classOf[MeasureUnit].isAssignableFrom(clazz)
  134. override def get[T](clazz: Class[T], registry: CodecRegistry): Codec[T] =
  135. if (isCaseObjectEnum(clazz)) MeasureUnitCodec.asInstanceOf[Codec[T]]
  136. else null
  137. object MeasureUnitCodec extends Codec[MeasureUnit] {
  138. override def decode(
  139. reader: BsonReader,
  140. decoderContext: DecoderContext
  141. ): MeasureUnit = MeasureUnit.fromString(reader.readString()).get
  142. override def encode(
  143. writer: BsonWriter,
  144. value: MeasureUnit,
  145. encoderContext: EncoderContext
  146. ): Unit = writer.writeString(value.toString)
  147. override def getEncoderClass: Class[MeasureUnit] =
  148. MeasureUnit.getClass.asInstanceOf[Class[MeasureUnit]]
  149. }
  150. }