MongoDBService.scala 5.9 KB

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