package com.weEat.services import play.api.Configuration import org.mongodb.scala.{MongoClient,MongoCollection} import org.mongodb.scala.bson.codecs.Macros._ import org.bson.{BsonReader,BsonWriter} import org.bson.codecs.{Codec,DecoderContext,EncoderContext} import org.bson.codecs.configuration.{CodecProvider,CodecRegistry} import org.bson.codecs.configuration.CodecRegistries._ import com.weEat.models._ import com.weEat.shared.models.{FoodNodeId,USDANodeId,Ingredient,UnitType,MeasureUnit,RecipeNodeId} import Ingredient._ import javax.inject.{Inject,Singleton} import scala.reflect.ClassTag import com.weEat.migrations.{Migration,Metadata} import scala.concurrent.ExecutionContext import scala.util.{Success,Failure} import gov.usda.nal.fdc.models.Nutrient @Singleton class MongoDBService @Inject()(config: Configuration) { implicit val ec = scala.concurrent.ExecutionContext.global private val prefix = "mongo" val url = config.getOptional[String](s"$prefix.url").getOrElse("localhost") val name = config.getOptional[String](s"$prefix.name").getOrElse("recipes") val user = config.getOptional[String](s"$prefix.user").getOrElse("application") val ssl = config.getOptional[Boolean](s"$prefix.ssl").getOrElse(true) val port = config.getOptional[Int](s"$prefix.port").getOrElse(27017) val codecRegistry = fromRegistries( fromProviders( WrapperCodecProvider(classOf[FoodNodeId], { n: USDANodeId => n.copy(nutrients = n.nutrients.map { case (k, v) => (k.replace(".", "$"), v) }) }, { n: USDANodeId => n.copy(nutrients = n.nutrients.map { case (k, v) => (k.replace("$", "."), v) }) }), WrapperCodecProvider(classOf[FoodNodeId], { n: RecipeNodeId => n }, { n: RecipeNodeId => n } ), classOf[Metadata], classOf[FoodNodeId], classOf[IngredientId], classOf[Ingredient], classOf[User], classOf[Authorization], classOf[Nutrient], UnitTypeEnumCodecProvider, MeasureUnitCodecProvider ), MongoClient.DEFAULT_CODEC_REGISTRY ) val con = { import org.mongodb.scala._ import org.mongodb.scala.connection.{SslSettings,ClusterSettings} import scala.jdk.CollectionConverters._ implicit val ec: ExecutionContext = ExecutionContext.global val password = Option(config.get[String](s"$prefix.password")) .getOrElse("").toCharArray() val credential = MongoCredential.createCredential(user, name, password) val db = MongoClient( MongoClientSettings.builder() .applyToSslSettings((builder: SslSettings.Builder) => builder.enabled(ssl) ).applyToClusterSettings((builder: ClusterSettings.Builder) => builder.hosts(List(new ServerAddress(url, port)).asJava) ).credential(credential) .build() ).getDatabase(name) .withCodecRegistry(codecRegistry) Migration.updateToLatest(db).map(_ => db) } def apply[T](coll: Collectable[T])(implicit ct: ClassTag[T]) = con.map(_.getCollection[T](coll.collectionName)) def withCollection[T, U](coll: Collectable[U])(fn: (MongoCollection[U] => T)) (implicit ct: ClassTag[U]) = apply(coll).transform({ case Success(collection) => Success(fn(collection)) case Failure(e) => Failure(e) }) } case class WrapperCodecProvider[T]( provider: CodecProvider, encodeFn: (T => T), decodeFn: (T => T) )(implicit m: ClassTag[T]) extends CodecProvider { override def get[U](c: Class[U], registry: CodecRegistry): Codec[U] = if (m.runtimeClass.isAssignableFrom(c) && c.isAssignableFrom(m.runtimeClass)) WrapperCodec(provider.get(c, registry))( encodeFn.asInstanceOf[U => U], decodeFn.asInstanceOf[U => U] ).asInstanceOf[Codec[U]] else null } case class WrapperCodec[T](val inner: Codec[T])( encodeFn: (T => T), decodeFn: (T => T) ) extends Codec[T] { override def decode( reader: BsonReader, decoderContext: DecoderContext ): T = decodeFn(inner.decode(reader, decoderContext)) override def encode( writer: BsonWriter, value: T, encoderContext: EncoderContext ): Unit = inner.encode(writer, encodeFn(value), encoderContext) override def getEncoderClass: Class[T] = inner.getEncoderClass } object UnitTypeEnumCodecProvider extends CodecProvider { def isCaseObjectEnum[T](clazz: Class[T]): Boolean = clazz.isInstance(UnitType.MASS) || clazz.isInstance(UnitType.VOLUME) || clazz.isInstance(UnitType.NUMBER) override def get[T](clazz: Class[T], registry: CodecRegistry): Codec[T] = if (isCaseObjectEnum(clazz)) UnitTypeEnumCodec.asInstanceOf[Codec[T]] else null object UnitTypeEnumCodec extends Codec[UnitType.UnitType] { override def decode( reader: BsonReader, decoderContext: DecoderContext ): UnitType.UnitType = UnitType.withName(reader.readString()) override def encode( writer: BsonWriter, value: UnitType.UnitType, encoderContext: EncoderContext ): Unit = writer.writeString(value.name) override def getEncoderClass: Class[UnitType.UnitType] = UnitType.getClass.asInstanceOf[Class[UnitType.UnitType]] } } object MeasureUnitCodecProvider extends CodecProvider { def isCaseObjectEnum[T](clazz: Class[T]): Boolean = classOf[MeasureUnit].isAssignableFrom(clazz) override def get[T](clazz: Class[T], registry: CodecRegistry): Codec[T] = if (isCaseObjectEnum(clazz)) MeasureUnitCodec.asInstanceOf[Codec[T]] else null object MeasureUnitCodec extends Codec[MeasureUnit] { override def decode( reader: BsonReader, decoderContext: DecoderContext ): MeasureUnit = MeasureUnit.fromString(reader.readString()).get override def encode( writer: BsonWriter, value: MeasureUnit, encoderContext: EncoderContext ): Unit = writer.writeString(value.toString) override def getEncoderClass: Class[MeasureUnit] = MeasureUnit.getClass.asInstanceOf[Class[MeasureUnit]] } }