MongoDBService.scala 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  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,RecipeMetaNodeId,FoodId}
  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[RecipeMetaNodeId],
  45. classOf[FoodId],
  46. classOf[FoodNodeId],
  47. classOf[IngredientId],
  48. classOf[Ingredient],
  49. classOf[User],
  50. classOf[Authorization],
  51. classOf[Nutrient],
  52. UnitTypeEnumCodecProvider,
  53. MeasureUnitCodecProvider
  54. ),
  55. MongoClient.DEFAULT_CODEC_REGISTRY
  56. )
  57. val con = {
  58. import org.mongodb.scala._
  59. import org.mongodb.scala.connection.{SslSettings,ClusterSettings}
  60. import scala.jdk.CollectionConverters._
  61. implicit val ec: ExecutionContext = ExecutionContext.global
  62. val password = Option(config.get[String](s"$prefix.password"))
  63. .getOrElse("").toCharArray()
  64. val credential = MongoCredential.createCredential(user, name, password)
  65. val db = MongoClient(
  66. MongoClientSettings.builder()
  67. .applyToSslSettings((builder: SslSettings.Builder) =>
  68. builder.enabled(ssl)
  69. ).applyToClusterSettings((builder: ClusterSettings.Builder) =>
  70. builder.hosts(List(new ServerAddress(url, port)).asJava)
  71. ).credential(credential)
  72. .build()
  73. ).getDatabase(name)
  74. .withCodecRegistry(codecRegistry)
  75. Migration.updateToLatest(db).map(_ => db)
  76. }
  77. def apply[T](coll: Collectable[T])(implicit ct: ClassTag[T]) =
  78. con.map(_.getCollection[T](coll.collectionName))
  79. def withCollection[T, U](coll: Collectable[U])(fn: (MongoCollection[U] => T))
  80. (implicit ct: ClassTag[U]) =
  81. apply(coll).transform({
  82. case Success(collection) => Success(fn(collection))
  83. case Failure(e) => Failure(e)
  84. })
  85. }
  86. case class WrapperCodecProvider[T](
  87. provider: CodecProvider,
  88. encodeFn: (T => T),
  89. decodeFn: (T => T)
  90. )(implicit m: ClassTag[T]) extends CodecProvider {
  91. override def get[U](c: Class[U], registry: CodecRegistry): Codec[U] =
  92. if (m.runtimeClass.isAssignableFrom(c) && c.isAssignableFrom(m.runtimeClass))
  93. WrapperCodec(provider.get(c, registry))(
  94. encodeFn.asInstanceOf[U => U],
  95. decodeFn.asInstanceOf[U => U]
  96. ).asInstanceOf[Codec[U]]
  97. else null
  98. }
  99. case class WrapperCodec[T](val inner: Codec[T])(
  100. encodeFn: (T => T),
  101. decodeFn: (T => T)
  102. ) extends Codec[T] {
  103. override def decode(
  104. reader: BsonReader,
  105. decoderContext: DecoderContext
  106. ): T = decodeFn(inner.decode(reader, decoderContext))
  107. override def encode(
  108. writer: BsonWriter,
  109. value: T,
  110. encoderContext: EncoderContext
  111. ): Unit = inner.encode(writer, encodeFn(value), encoderContext)
  112. override def getEncoderClass: Class[T] = inner.getEncoderClass
  113. }
  114. object UnitTypeEnumCodecProvider extends CodecProvider {
  115. def isCaseObjectEnum[T](clazz: Class[T]): Boolean =
  116. clazz.isInstance(UnitType.MASS) ||
  117. clazz.isInstance(UnitType.VOLUME) ||
  118. clazz.isInstance(UnitType.NUMBER)
  119. override def get[T](clazz: Class[T], registry: CodecRegistry): Codec[T] =
  120. if (isCaseObjectEnum(clazz)) UnitTypeEnumCodec.asInstanceOf[Codec[T]]
  121. else null
  122. object UnitTypeEnumCodec extends Codec[UnitType.UnitType] {
  123. override def decode(
  124. reader: BsonReader,
  125. decoderContext: DecoderContext
  126. ): UnitType.UnitType = UnitType.withName(reader.readString())
  127. override def encode(
  128. writer: BsonWriter,
  129. value: UnitType.UnitType,
  130. encoderContext: EncoderContext
  131. ): Unit = writer.writeString(value.name)
  132. override def getEncoderClass: Class[UnitType.UnitType] =
  133. UnitType.getClass.asInstanceOf[Class[UnitType.UnitType]]
  134. }
  135. }
  136. object MeasureUnitCodecProvider extends CodecProvider {
  137. def isCaseObjectEnum[T](clazz: Class[T]): Boolean =
  138. classOf[MeasureUnit].isAssignableFrom(clazz)
  139. override def get[T](clazz: Class[T], registry: CodecRegistry): Codec[T] =
  140. if (isCaseObjectEnum(clazz)) MeasureUnitCodec.asInstanceOf[Codec[T]]
  141. else null
  142. object MeasureUnitCodec extends Codec[MeasureUnit] {
  143. override def decode(
  144. reader: BsonReader,
  145. decoderContext: DecoderContext
  146. ): MeasureUnit = MeasureUnit.fromString(reader.readString()).get
  147. override def encode(
  148. writer: BsonWriter,
  149. value: MeasureUnit,
  150. encoderContext: EncoderContext
  151. ): Unit = writer.writeString(value.toString)
  152. override def getEncoderClass: Class[MeasureUnit] =
  153. MeasureUnit.getClass.asInstanceOf[Class[MeasureUnit]]
  154. }
  155. }