Skip to content

Commit

Permalink
APID-520 - Porting hmrc mongo driver - Initial changes
Browse files Browse the repository at this point in the history
  • Loading branch information
sivaprakashiv committed Oct 12, 2022
1 parent c0f3a1f commit f93d284
Show file tree
Hide file tree
Showing 12 changed files with 603 additions and 420 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package uk.gov.hmrc.apisubscriptionfields

import java.util.UUID

import org.scalatest._
import org.scalatestplus.play.guice.GuiceOneServerPerSuite
import play.api.Application
Expand All @@ -27,14 +26,14 @@ import play.api.mvc._
import play.api.mvc.request.RequestTarget
import play.api.test.FakeRequest
import play.api.test.Helpers._
import play.modules.reactivemongo.ReactiveMongoComponent
import uk.gov.hmrc.apisubscriptionfields.model._

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}
import cats.data.NonEmptyList
import uk.gov.hmrc.apisubscriptionfields.controller.Helper
import uk.gov.hmrc.mongo.MongoComponent

trait AcceptanceTestSpec extends FeatureSpec
with GivenWhenThen
Expand Down Expand Up @@ -97,7 +96,7 @@ trait AcceptanceTestSpec extends FeatureSpec
}

private def dropDatabase(): Unit = {
await( app.injector.instanceOf[ReactiveMongoComponent].mongoConnector.db().drop())
await( app.injector.instanceOf[MongoComponent].database.drop().toFuture())
}

def createRequest(method: String, path: String) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,24 @@

package uk.gov.hmrc.apisubscriptionfields.repository

import javax.inject.{Inject, Singleton}
import akka.stream.Materializer
import com.google.inject.ImplementedBy
import org.bson.codecs.configuration.CodecRegistries.{fromCodecs, fromRegistries}
import org.mongodb.scala.model.Filters.{and, equal}
import org.mongodb.scala.model.Indexes.ascending
import org.mongodb.scala.model.{Filters, IndexModel, IndexOptions}
import org.mongodb.scala.{MongoClient, MongoCollection}
import play.api.libs.json._
import reactivemongo.api.indexes.IndexType
import reactivemongo.bson.BSONObjectID
import reactivemongo.play.json.collection.JSONCollection
import uk.gov.hmrc.mongo.ReactiveRepository
import uk.gov.hmrc.mongo.json.ReactiveMongoFormats
import uk.gov.hmrc.apisubscriptionfields.model.JsonFormatters.ApiFieldDefinitionsJF
import uk.gov.hmrc.apisubscriptionfields.model.Types._
import uk.gov.hmrc.apisubscriptionfields.model._
import Types._
import uk.gov.hmrc.apisubscriptionfields.utils.ApplicationLogger
import uk.gov.hmrc.mongo.MongoComponent
import uk.gov.hmrc.mongo.play.json.{Codecs, CollectionFactory, PlayMongoRepository}

import scala.concurrent.Future
import javax.inject.{Inject, Singleton}
import scala.concurrent.{ExecutionContext, Future}
import scala.util.control.NonFatal

@ImplementedBy(classOf[ApiFieldDefinitionsMongoRepository])
trait ApiFieldDefinitionsRepository {
Expand All @@ -42,53 +48,83 @@ trait ApiFieldDefinitionsRepository {
}

@Singleton
class ApiFieldDefinitionsMongoRepository @Inject() (mongoDbProvider: MongoDbProvider)
extends ReactiveRepository[ApiFieldDefinitions, BSONObjectID]("fieldsDefinitions", mongoDbProvider.mongo, JsonFormatters.ApiFieldDefinitionsJF, ReactiveMongoFormats.objectIdFormats)
class ApiFieldDefinitionsMongoRepository @Inject() (mongo: MongoComponent)
(implicit ec: ExecutionContext, val mat: Materializer)
extends PlayMongoRepository[ApiFieldDefinitions](
collectionName = "fieldsDefinitions",
mongoComponent = mongo,
domainFormat = JsonFormatters.ApiFieldDefinitionsJF,
indexes = Seq(
IndexModel(ascending(List("apiContext", "apiVersion"): _*),
IndexOptions()
.name("apiContext-apiVersion_index")
.background(true)
.unique(true))
))
with ApiFieldDefinitionsRepository
with MongoCrudHelper[ApiFieldDefinitions] {

override val mongoCollection: JSONCollection = collection

override def indexes = Seq(
createCompoundIndex(
indexFieldMappings = Seq(
"apiContext" -> IndexType.Ascending,
"apiVersion" -> IndexType.Ascending
),
indexName = Some("apiContext-apiVersion_index"),
isUnique = true
)
)

override def save(definitions: ApiFieldDefinitions): Future[(ApiFieldDefinitions, IsInsert)] = {
import JsonFormatters.ApiFieldDefinitionsJF
save(definitions, selectorFor(definitions))
with ApplicationLogger {
// with MongoCrudHelper[ApiFieldDefinitions] {

override lazy val collection: MongoCollection[ApiFieldDefinitions] =
CollectionFactory
.collection(mongo.database, collectionName, domainFormat)
.withCodecRegistry(
fromRegistries(
fromCodecs(
Codecs.playFormatCodec(domainFormat),
Codecs.playFormatCodec(JsonFormatters.ApiContextJF),
Codecs.playFormatCodec(JsonFormatters.ApiVersionJF),
Codecs.playFormatCodec(JsonFormatters.ApiFieldDefinitionsJF),
Codecs.playFormatCodec(JsonFormatters.ValidationJF)
),
MongoClient.DEFAULT_CODEC_REGISTRY
)
)

def save(definitions: ApiFieldDefinitions): Future[(ApiFieldDefinitions, IsInsert)] = {
val query = and(equal("apiContext", Codecs.toBson(definitions.apiContext.value)),
equal("apiVersion", Codecs.toBson(definitions.apiVersion.value)))

collection.find(query).headOption flatMap {
case Some(_: ApiFieldDefinitions) =>
for {
updatedDefinitions <- collection.replaceOne(
filter = query,
replacement = definitions
).toFuture().map(_ => definitions)
} yield (updatedDefinitions, false)

case None =>
for {
newDefinitions <- collection.insertOne(definitions).toFuture().map(_ => definitions)
} yield (newDefinitions, true)
}

// collection
// .findOneAndUpdate(Filters.and(equal("apiContext", Codecs.toBson(definitions.apiContext.value)),
// equal("apiVersion", Codecs.toBson(definitions.apiVersion.value))),
// definitions
// ).map(_.asInstanceOf[ApiFieldDefinitions]).head
}

override def fetch(apiContext: ApiContext, apiVersion: ApiVersion): Future[Option[ApiFieldDefinitions]] = {
getOne(selectorFor(apiContext, apiVersion))
collection.find(Filters.and(equal("apiContext", Codecs.toBson(apiContext.value)),
equal("apiVersion", Codecs.toBson(apiVersion.value)))).headOption()
}

override def fetchAll(): Future[List[ApiFieldDefinitions]] = {
getMany(Json.obj())
collection.find().toFuture().map(_.toList)
}

override def delete(apiContext: ApiContext, apiVersion: ApiVersion): Future[Boolean] = {
deleteOne(selectorFor(apiContext, apiVersion))
}

private def selectorFor(apiContext: ApiContext, apiVersion: ApiVersion): JsObject = {
selector(apiContext, apiVersion)
}

private def selectorFor(fd: ApiFieldDefinitions): JsObject = {
selector(fd.apiContext, fd.apiVersion)
}

private def selector(apiContext: ApiContext, apiVersion: ApiVersion): JsObject = {
Json.obj(
"apiContext" -> apiContext.value,
"apiVersion" -> apiVersion.value
)
collection.deleteOne(Filters.and(equal("apiContext", Codecs.toBson(apiContext.value)),
equal("apiVersion", Codecs.toBson(apiVersion.value))))
.toFuture()
.map(_.wasAcknowledged())

// .head().map(result => result.getDeletedCount > 0) recoverWith {
// case NonFatal(e) =>
// appLogger.error(s"Could not delete entity for apiContext ${apiContext.value} and apiVersion ${apiVersion.value}")
// Future.successful(false)}
}
}
135 changes: 74 additions & 61 deletions app/uk/gov/hmrc/apisubscriptionfields/repository/MongoCrudHelper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,64 +14,77 @@
* limitations under the License.
*/

package uk.gov.hmrc.apisubscriptionfields.repository

import play.api.libs.json._
import reactivemongo.play.json._
import reactivemongo.play.json.collection.JSONCollection

import reactivemongo.api.Cursor

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import uk.gov.hmrc.apisubscriptionfields.model.Types.IsInsert

trait MongoCrudHelper[T] extends MongoIndexCreator with MongoErrorHandler {

protected val mongoCollection: JSONCollection

def saveAtomic(selector: JsObject, updateOperations: JsObject)(implicit w: OFormat[T]): Future[(T, IsInsert)] = {
val updateOp = mongoCollection.updateModifier(
update = updateOperations,
fetchNewObject = true,
upsert = true
)

mongoCollection.findAndModify(selector, updateOp).map { findAndModifyResult =>
val maybeTuple: Option[(T, IsInsert)] = for {
value <- findAndModifyResult.value
updateLastError <- findAndModifyResult.lastError
} yield (value.as[T], !updateLastError.updatedExisting)

maybeTuple.fold[(T, IsInsert)] {
handleError(selector, findAndModifyResult)
}(tuple => tuple)
}
}

private def handleError(selector: JsObject, findAndModifyResult: mongoCollection.BatchCommands.FindAndModifyCommand.FindAndModifyResult) = {
val error = s"Error upserting database for $selector."
appLogger.error(s"$error lastError: ${findAndModifyResult.lastError}")
throw new RuntimeException(error)
}

def save(entity: T, selector: JsObject)(implicit w: OWrites[T]): Future[(T, IsInsert)] = {
mongoCollection.update(ordered=false).one(selector, entity, upsert = true).map { updateWriteResult => (entity, handleSaveError(updateWriteResult, s"Could not save entity: $entity")) }
}

def getMany(selector: JsObject)(implicit r: Reads[T]): Future[List[T]] = {
mongoCollection.find[JsObject, JsObject](selector, None).cursor[T]().collect[List](Int.MaxValue, Cursor.FailOnError[List[T]]())
}

def getOne(selector: JsObject)(implicit r: Reads[T]): Future[Option[T]] = {
mongoCollection.find[JsObject, JsObject](selector, None).one[T]
}

def deleteOne(selector: JsObject): Future[Boolean] = {
mongoCollection.delete(ordered=false).one(selector, limit = Some(1)).map(handleDeleteError(_, s"Could not delete entity for selector: $selector"))
}

def deleteMany(selector: JsObject): Future[Boolean] = {
mongoCollection.delete().one(selector).map(handleDeleteError(_, s"Could not delete entity for selector: $selector"))
}
}
///*
// * Copyright 2022 HM Revenue & Customs
// *
// * Licensed under the Apache License, Version 2.0 (the "License");
// * you may not use this file except in compliance with the License.
// * You may obtain a copy of the License at
// *
// * http://www.apache.org/licenses/LICENSE-2.0
// *
// * Unless required by applicable law or agreed to in writing, software
// * distributed under the License is distributed on an "AS IS" BASIS,
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// * See the License for the specific language governing permissions and
// * limitations under the License.
// */
//
//package uk.gov.hmrc.apisubscriptionfields.repository
//
//import org.mongodb.scala.MongoCollection
//import play.api.libs.json._
//
//import scala.concurrent.ExecutionContext.Implicits.global
//import scala.concurrent.Future
//import uk.gov.hmrc.apisubscriptionfields.model.Types.IsInsert
//
//trait MongoCrudHelper[T] extends MongoIndexCreator with MongoErrorHandler {
//
// protected val collection: MongoCollection[T]
//
// def saveAtomic(selector: JsObject, updateOperations: JsObject)(implicit w: OFormat[T]): Future[(T, IsInsert)] = {
// val updateOp = collection.updateModifier(
// update = updateOperations,
// fetchNewObject = true,
// upsert = true
// )
//
// collection.findAndModify(selector, updateOp).map { findAndModifyResult =>
// val maybeTuple: Option[(T, IsInsert)] = for {
// value <- findAndModifyResult.value
// updateLastError <- findAndModifyResult.lastError
// } yield (value.as[T], !updateLastError.updatedExisting)
//
// maybeTuple.fold[(T, IsInsert)] {
// handleError(selector, findAndModifyResult)
// }(tuple => tuple)
// }
// }
//
// private def handleError(selector: JsObject, findAndModifyResult: collection.BatchCommands.FindAndModifyCommand.FindAndModifyResult) = {
// val error = s"Error upserting database for $selector."
// appLogger.error(s"$error lastError: ${findAndModifyResult.lastError}")
// throw new RuntimeException(error)
// }
//
// def save(entity: T, selector: JsObject)(implicit w: OWrites[T]): Future[(T, IsInsert)] = {
// collection.findOneAndUpdate(ordered=false).one(selector, entity, upsert = true).map { updateWriteResult => (entity, handleSaveError(updateWriteResult, s"Could not save entity: $entity")) }
// }
//
// def getMany(selector: JsObject)(implicit r: Reads[T]): Future[List[T]] = {
// collection.find[JsObject, JsObject](selector, None).cursor[T]().collect[List](Int.MaxValue, Cursor.FailOnError[List[T]]())
// }
//
// def getOne(selector: JsObject)(implicit r: Reads[T]): Future[Option[T]] = {
// collection.find[JsObject, JsObject](selector, None).one[T]
// }
//
// def deleteOne(selector: JsObject): Future[Boolean] = {
// collection.delete(ordered=false).one(selector, limit = Some(1)).map(handleDeleteError(_, s"Could not delete entity for selector: $selector"))
// }
//
// def deleteMany(selector: JsObject): Future[Boolean] = {
// collection.delete().one(selector).map(handleDeleteError(_, s"Could not delete entity for selector: $selector"))
// }
//}
46 changes: 30 additions & 16 deletions app/uk/gov/hmrc/apisubscriptionfields/repository/MongoDb.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,33 @@
* limitations under the License.
*/

package uk.gov.hmrc.apisubscriptionfields.repository

import com.google.inject.ImplementedBy
import javax.inject.{Inject, Singleton}
import play.modules.reactivemongo.ReactiveMongoComponent
import reactivemongo.api.DB

@ImplementedBy(classOf[MongoDb])
trait MongoDbProvider {
def mongo: () => DB
}

@Singleton
class MongoDb @Inject()(component: ReactiveMongoComponent) extends MongoDbProvider {
override val mongo: () => DB = component.mongoConnector.db
}
///*
// * Copyright 2022 HM Revenue & Customs
// *
// * Licensed under the Apache License, Version 2.0 (the "License");
// * you may not use this file except in compliance with the License.
// * You may obtain a copy of the License at
// *
// * http://www.apache.org/licenses/LICENSE-2.0
// *
// * Unless required by applicable law or agreed to in writing, software
// * distributed under the License is distributed on an "AS IS" BASIS,
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// * See the License for the specific language governing permissions and
// * limitations under the License.
// */
//
//package uk.gov.hmrc.apisubscriptionfields.repository
//
//import com.google.inject.ImplementedBy
//import javax.inject.{Inject, Singleton}
//
//@ImplementedBy(classOf[MongoDb])
//trait MongoDbProvider {
// def mongo: () => DB
//}
//
//@Singleton
//class MongoDb @Inject()(component: ReactiveMongoComponent) extends MongoDbProvider {
// override val mongo: () => DB = component.mongoConnector.db
//}
Loading

0 comments on commit f93d284

Please sign in to comment.