From 49aa1bbe982d2bf4176ed8ca8a635ef8548841d9 Mon Sep 17 00:00:00 2001 From: shuang Date: Fri, 25 Oct 2019 10:57:29 +0800 Subject: [PATCH] feature/persist_DynaicEntity_to_DB --- .../main/scala/bootstrap/liftweb/Boot.scala | 2 + .../scala/code/api/util/ErrorMessages.scala | 1 + .../main/scala/code/api/util/NewStyle.scala | 2 +- .../scala/code/api/v4_0_0/APIMethods400.scala | 15 ++++- .../code/api/v4_0_0/DynamicEntityHelper.scala | 28 +-------- .../scala/code/api/v4_0_0/OBPAPI4_0_0.scala | 3 +- .../bankconnectors/LocalMappedConnector.scala | 40 ++++++++----- .../dynamicEntity/DynamicDataProvider.scala | 41 +++++++++++++ .../MapppedDynamicDataProvider.scala | 60 +++++++++++++++++++ .../code/api/v4_0_0/DynamicEntityTest.scala | 2 +- .../commons/model/enums/Enumerations.scala | 1 + 11 files changed, 149 insertions(+), 46 deletions(-) create mode 100644 obp-api/src/main/scala/code/dynamicEntity/DynamicDataProvider.scala create mode 100644 obp-api/src/main/scala/code/dynamicEntity/MapppedDynamicDataProvider.scala diff --git a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala index 02c9ec0ce..8d84d4b53 100644 --- a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala +++ b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala @@ -29,6 +29,7 @@ package bootstrap.liftweb import java.io.{File, FileInputStream} import java.util.{Locale, TimeZone} +import code.DynamicData.DynamicData import code.accountapplication.MappedAccountApplication import code.accountattribute.MappedAccountAttribute import code.accountholders.MapperAccountHolders @@ -660,6 +661,7 @@ object ToSchemify { WebUiProps, Authorisation, DynamicEntity, + DynamicData, AccountIdMapping, )++ APIBuilder_Connector.allAPIBuilderModels } diff --git a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala index d4ab1c47f..880d07371 100644 --- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala +++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala @@ -40,6 +40,7 @@ object ErrorMessages { val DynamicEntityNotExists = "OBP-09003: DynamicEntity not exists. Please check entityName." val DynamicEntityMissArgument = "OBP-09004: DynamicEntity process related argument is missing." val EntityNotFoundByEntityId = "OBP-09005: Entity not found. Please specify a valid value for entityId." + val DynamicEntityOperationNotAllowed = "OBP-09006: Operation is not allowed, because Current DynamicEntity have upload data, must to delete all the data before this operation." // General messages (OBP-10XXX) val InvalidJsonFormat = "OBP-10001: Incorrect json format." diff --git a/obp-api/src/main/scala/code/api/util/NewStyle.scala b/obp-api/src/main/scala/code/api/util/NewStyle.scala index 68fe661fb..3e980b55f 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -1452,7 +1452,7 @@ object NewStyle { def getDynamicEntityById(dynamicEntityId : String, callContext: Option[CallContext]): OBPReturnType[DynamicEntityT] = { val dynamicEntityBox: Box[DynamicEntityT] = DynamicEntityProvider.connectorMethodProvider.vend.getById(dynamicEntityId) - val dynamicEntity = unboxFullOrFail(dynamicEntityBox, callContext, DynamicEntityNotFoundByDynamicEntityId) + val dynamicEntity = unboxFullOrFail(dynamicEntityBox, callContext, DynamicEntityNotFoundByDynamicEntityId, 404) Future{ (dynamicEntity, callContext) } diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 21899749d..250ea46e9 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -5,7 +5,7 @@ import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._ import code.api.util.APIUtil._ import code.api.util.ApiRole._ import code.api.util.ApiTag._ -import code.api.util.ErrorMessages.{AccountNotFound, AllowedAttemptsUsedUp, BankAccountNotFound, BankNotFound, CounterpartyBeneficiaryPermit, InsufficientAuthorisationToCreateTransactionRequest, InvalidAccountIdFormat, InvalidBankIdFormat, InvalidChallengeAnswer, InvalidChallengeType, InvalidChargePolicy, InvalidISOCurrencyCode, InvalidJsonFormat, InvalidNumber, InvalidTransactionRequesChallengeId, InvalidTransactionRequestCurrency, InvalidTransactionRequestType, NoViewPermission, NotPositiveAmount, TransactionDisabled, TransactionRequestStatusNotInitiated, TransactionRequestTypeHasChanged, UnknownError, UserHasMissingRoles, UserNoPermissionAccessView, UserNotLoggedIn, ViewNotFound} +import code.api.util.ErrorMessages.{AccountNotFound, AllowedAttemptsUsedUp, BankAccountNotFound, BankNotFound, CounterpartyBeneficiaryPermit, InsufficientAuthorisationToCreateTransactionRequest, InvalidAccountIdFormat, InvalidBankIdFormat, InvalidChallengeAnswer, InvalidChallengeType, InvalidChargePolicy, InvalidISOCurrencyCode, InvalidJsonFormat, InvalidNumber, InvalidTransactionRequesChallengeId, InvalidTransactionRequestCurrency, InvalidTransactionRequestType, NoViewPermission, NotPositiveAmount, TransactionDisabled, TransactionRequestStatusNotInitiated, TransactionRequestTypeHasChanged, UnknownError, UserHasMissingRoles, UserNoPermissionAccessView, UserNotLoggedIn, ViewNotFound, DynamicEntityOperationNotAllowed} import code.api.util.ExampleValue.{dynamicEntityRequestBodyExample, dynamicEntityResponseBodyExample} import code.api.util.NewStyle.HttpCode import code.api.util._ @@ -808,6 +808,13 @@ trait APIMethods400 { (Full(u), callContext) <- authorizedAccess(cc) _ <- NewStyle.function.hasEntitlement("", u.userId, canUpdateDynamicEntity, callContext) + // Check whether there are uploaded data, only if no uploaded data allow to update DynamicEntity. + (entity, _) <- NewStyle.function.getDynamicEntityById(dynamicEntityId, callContext) + (isExists, _) <- NewStyle.function.invokeDynamicConnector(IS_EXISTS_DATA, entity.entityName, None, None, callContext) + _ <- Helper.booleanToFuture(DynamicEntityOperationNotAllowed) { + isExists.isDefined && isExists.contains(JBool(false)) + } + jsonObject = json.asInstanceOf[JObject] dynamicEntity = DynamicEntityCommons(jsonObject, Some(dynamicEntityId)) Full(result) <- NewStyle.function.createOrUpdateDynamicEntity(dynamicEntity, callContext) @@ -848,6 +855,12 @@ trait APIMethods400 { for { (Full(u), callContext) <- authorizedAccess(cc) _ <- NewStyle.function.hasEntitlement("", u.userId, canDeleteDynamicEntity, callContext) + // Check whether there are uploaded data, only if no uploaded data allow to delete DynamicEntity. + (entity, _) <- NewStyle.function.getDynamicEntityById(dynamicEntityId, callContext) + (isExists, _) <- NewStyle.function.invokeDynamicConnector(IS_EXISTS_DATA, entity.entityName, None, None, callContext) + _ <- Helper.booleanToFuture(DynamicEntityOperationNotAllowed) { + isExists.isDefined && isExists.contains(JBool(false)) + } deleted: Box[Boolean] <- NewStyle.function.deleteDynamicEntity(dynamicEntityId) } yield { (deleted, HttpCode.`200`(callContext)) diff --git a/obp-api/src/main/scala/code/api/v4_0_0/DynamicEntityHelper.scala b/obp-api/src/main/scala/code/api/v4_0_0/DynamicEntityHelper.scala index 2f57b2836..999fbccb6 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/DynamicEntityHelper.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/DynamicEntityHelper.scala @@ -2,9 +2,8 @@ package code.api.v4_0_0 import code.api.util.APIUtil.{Catalogs, ResourceDoc, authenticationRequiredMessage, emptyObjectJson, generateUUID, notCore, notOBWG, notPSD2} import code.api.util.ApiTag.{apiTagApi, apiTagNewStyle} -import code.api.util.ErrorMessages.{InvalidJsonFormat, InvalidUrl, UnknownError, UserHasMissingRoles, UserNotLoggedIn} +import code.api.util.ErrorMessages.{InvalidJsonFormat, UnknownError, UserHasMissingRoles, UserNotLoggedIn} import code.api.util.{ApiTag, ApiVersion, NewStyle} -import net.liftweb.common.Box import net.liftweb.json.JsonDSL._ import net.liftweb.json._ import net.liftweb.util.StringHelpers @@ -30,31 +29,6 @@ object MockerConnector { def definitionsMap = NewStyle.function.getDynamicEntities().map(it => (it.entityName, DynamicEntityInfo(it.metadataJson, it.entityName))).toMap - //(id, entityName) -> entity - val persistedEntities = scala.collection.mutable.Map[(String, String), JObject]() - - def persist(entityName: String, requestBody: JObject, id: Option[String] = None) = { - val idValue = id.orElse(Some(generateUUID())) - val idName = StringUtils.uncapitalize(entityName) + "Id" - val entityToPersist = this.definitionsMap(entityName).toResponse(requestBody, id) - val haveIdEntity = (entityToPersist \ idName) match { - case JNothing => JObject(JField(idName, JString(idValue.get)) :: entityToPersist.obj) - case _ => entityToPersist - } - persistedEntities.put((idValue.get, entityName), haveIdEntity) - haveIdEntity - } - - def getSingle(entityName: String, id: String) = { - persistedEntities.get(id, entityName) - } - - def getAll(entityName: String) = persistedEntities.filter(pair => pair._1._2 == entityName).values - - def delete(entityName: String, id: String): Box[Boolean] = { - persistedEntities.remove(id -> entityName).map(_ => true) - } - def doc = { val docs: Seq[ResourceDoc] = definitionsMap.values.flatMap(createDocs).toSeq collection.mutable.ArrayBuffer(docs:_*) diff --git a/obp-api/src/main/scala/code/api/v4_0_0/OBPAPI4_0_0.scala b/obp-api/src/main/scala/code/api/v4_0_0/OBPAPI4_0_0.scala index 533d7b333..39d261225 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/OBPAPI4_0_0.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/OBPAPI4_0_0.scala @@ -56,7 +56,7 @@ object OBPAPI4_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w val endpointsOf4_0_0 = getEndpoints(Implementations4_0_0) - Implementations4_0_0.genericEndpoint // if old version ResourceDoc objects have the same name endpoint with new version, omit old version ResourceDoc. - val allResourceDocs = collectResourceDocs(OBPAPI3_1_0.allResourceDocs, + def allResourceDocs = collectResourceDocs(OBPAPI3_1_0.allResourceDocs, Implementations4_0_0.resourceDocs, MockerConnector.doc) // all endpoints @@ -68,6 +68,7 @@ object OBPAPI4_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w // Filter the possible endpoints by the disabled / enabled Props settings and add them together val routes : List[OBPEndpoint] = + Implementations4_0_0.genericEndpoint:: Implementations4_0_0.root :: // For now we make this mandatory getAllowedEndpoints(endpoints, allResourceDocs) diff --git a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala index d65f22ba9..cf13ca882 100644 --- a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala @@ -3,6 +3,7 @@ package code.bankconnectors import java.util.Date import java.util.UUID.randomUUID +import code.DynamicData.DynamicDataProvider import code.accountapplication.AccountApplicationX import code.accountattribute.AccountAttributeX import code.accountholders.AccountHolders @@ -10,10 +11,7 @@ import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON import code.api.cache.Caching import code.api.util.APIUtil.{OBPReturnType, isValidCurrencyISOCode, saveConnectorMetric, stringOrNull} import code.api.util.ErrorMessages._ -import com.openbankproject.commons.model.enums.StrongCustomerAuthentication.SCA import code.api.util._ -import code.api.v4_0_0.MockerConnector -import code.api.v4_0_0.MockerConnector.persistedEntities import code.atms.Atms.Atm import code.atms.MappedAtm import code.branches.Branches.Branch @@ -54,12 +52,14 @@ import code.views.Views import com.google.common.cache.CacheBuilder import com.nexmo.client.NexmoClient import com.nexmo.client.sms.messages.TextMessage -import com.openbankproject.commons.model.enums.DynamicEntityOperation.{CREATE, DELETE, GET_ALL, GET_ONE, UPDATE} -import com.openbankproject.commons.model.enums.{AccountAttributeType, CardAttributeType, DynamicEntityOperation, ProductAttributeType, StrongCustomerAuthentication} +import com.openbankproject.commons.model.enums.DynamicEntityOperation._ +import com.openbankproject.commons.model.enums.StrongCustomerAuthentication.SCA +import com.openbankproject.commons.model.enums._ import com.openbankproject.commons.model.{AccountApplication, AccountAttribute, Product, ProductAttribute, ProductCollectionItem, TaxResidence, _} import com.tesobe.CacheKeyFromArguments import com.tesobe.model.UpdateBankAccount import net.liftweb.common._ +import net.liftweb.json import net.liftweb.json.{JArray, JBool, JObject, JValue} import net.liftweb.mapper.{By, _} import net.liftweb.util.Helpers.{tryo, _} @@ -2819,31 +2819,41 @@ object LocalMappedConnector extends Connector with MdcLoggable { return Helper.booleanToFuture(s"$InvalidJsonFormat entityId is required for $operation operation.")(entityId.isEmpty || StringUtils.isBlank(entityId.get)) .map(it => (it.map(_.asInstanceOf[JValue]), callContext)) } - if (!persistedEntities.contains(entityId.get -> entityName)) { - val id = entityId.get + val id = entityId.get + val value = DynamicDataProvider.connectorMethodProvider.vend.get(entityName, id) + if (value.isEmpty) { return Helper.booleanToFuture(s"$EntityNotFoundByEntityId please check: entityId = $id", 404)(false) .map(it => (it.map(_.asInstanceOf[JValue]), callContext)) } } - + val op: Any = operation Future { - val processResult: Box[JValue] = operation match { + val processResult: Box[JValue] = op match { case GET_ALL => Full { - JArray(MockerConnector.getAll(entityName).toList) + val dataList = DynamicDataProvider.connectorMethodProvider.vend.getAll(entityName) + JArray(dataList) } case GET_ONE => { - val boxedEntity: Box[JValue] = MockerConnector.getSingle(entityName, entityId.getOrElse(throw new RuntimeException(s"$DynamicEntityMissArgument the entityId is required."))) + val boxedEntity: Box[JValue] = DynamicDataProvider.connectorMethodProvider.vend + .get(entityName, entityId.getOrElse(throw new RuntimeException(s"$DynamicEntityMissArgument the entityId is required."))) + .map(it => json.parse(it.dataJson)) boxedEntity } case CREATE | UPDATE => { val body = requestBody.getOrElse(throw new RuntimeException(s"$DynamicEntityMissArgument please supply the requestBody.")) val id = if(operation == CREATE) None else entityId - val persistedEntity = MockerConnector.persist(entityName, body, id) - Full(persistedEntity) + val boxedEntity: Box[JValue] = DynamicDataProvider.connectorMethodProvider.vend.saveOrUpdate(entityName, body, id) + .map(it => json.parse(it.dataJson)) + boxedEntity } case DELETE => { - val deleteResult = MockerConnector.delete(entityName, entityId.getOrElse(throw new RuntimeException(s"$DynamicEntityMissArgument the entityId is required. "))) - deleteResult.map(JBool(_)) + val id = entityId.getOrElse(throw new RuntimeException(s"$DynamicEntityMissArgument the entityId is required. ")) + val deleteResult: Boolean = DynamicDataProvider.connectorMethodProvider.vend.delete(entityName, id) + Full(JBool(deleteResult)) + } + case IS_EXISTS_DATA => { + val isExistsData: Boolean = DynamicDataProvider.connectorMethodProvider.vend.existsData(entityName) + Full(JBool(isExistsData)) } } (processResult, callContext) diff --git a/obp-api/src/main/scala/code/dynamicEntity/DynamicDataProvider.scala b/obp-api/src/main/scala/code/dynamicEntity/DynamicDataProvider.scala new file mode 100644 index 000000000..14f9f0f9d --- /dev/null +++ b/obp-api/src/main/scala/code/dynamicEntity/DynamicDataProvider.scala @@ -0,0 +1,41 @@ +package code.DynamicData + +import com.openbankproject.commons.model.{Converter, JsonFieldReName} +import net.liftweb.common.Box +import net.liftweb.json.JObject +import net.liftweb.util.SimpleInjector + +object DynamicDataProvider extends SimpleInjector { + + val connectorMethodProvider = new Inject(buildOne _) {} + + def buildOne: MappedDynamicDataProvider.type = MappedDynamicDataProvider +} + +trait DynamicDataT { + def dynamicDataId: Option[String] + def dynamicEntityName: String + def dataJson: String +} + +case class DynamicDataCommons(dynamicEntityName: String, + dataJson: String, + dynamicDataId: Option[String] = None + ) extends DynamicDataT with JsonFieldReName + +object DynamicDataCommons extends Converter[DynamicDataT, DynamicDataCommons] + + +trait DynamicDataProvider { + def saveOrUpdate(entityName: String, requestBody: JObject, id: Option[String] = None): Box[DynamicData] + def get(entityName: String, id: String): Box[DynamicData] + def getAll(entityName: String): List[JObject] + def delete(entityName: String, id: String): Boolean + def existsData(dynamicEntityName: String): Boolean +} + + + + + + diff --git a/obp-api/src/main/scala/code/dynamicEntity/MapppedDynamicDataProvider.scala b/obp-api/src/main/scala/code/dynamicEntity/MapppedDynamicDataProvider.scala new file mode 100644 index 000000000..0735f96bb --- /dev/null +++ b/obp-api/src/main/scala/code/dynamicEntity/MapppedDynamicDataProvider.scala @@ -0,0 +1,60 @@ +package code.DynamicData + +import code.api.util.APIUtil.generateUUID +import code.api.util.CustomJsonFormats +import code.util.MappedUUID +import net.liftweb.common.Box +import net.liftweb.json +import net.liftweb.json.JObject +import net.liftweb.json.JsonDSL._ +import net.liftweb.mapper._ +import net.liftweb.util.Helpers.tryo +import org.apache.commons.lang3.StringUtils + +object MappedDynamicDataProvider extends DynamicDataProvider with CustomJsonFormats{ + override def saveOrUpdate(entityName: String, requestBody: JObject, id: Option[String]): Box[DynamicData] = { + val idValue: String = id.getOrElse(generateUUID()) + val dynamicData: DynamicData = id match{ + case Some(i) => get(entityName, i).openOrThrowException(s"not exists DynamicData's data of dynamicEntityName=$entityName, dynameicDataId=$i") + case _ => DynamicData.create.DynamicDataId(idValue).DynamicEntityName(entityName) + } + val idName = StringUtils.uncapitalize(entityName) + "Id" + + val dataToPersist: JObject = requestBody ~ (idName -> idValue) + val dataStr = json.compactRender(dataToPersist) + tryo { + dynamicData.DataJson(dataStr).saveMe() + } + } + + override def get(entityName: String, id: String): Box[DynamicData] = DynamicData.find(By(DynamicData.DynamicDataId, id)) + + override def getAll(entityName: String): List[JObject] = DynamicData.findAll(By(DynamicData.DynamicEntityName, entityName)) + .map(it => json.parse(it.dataJson)).map(_.asInstanceOf[JObject]) + + override def delete(entityName: String, id: String): Boolean = DynamicData.bulkDelete_!!(By(DynamicData.DynamicDataId, id)) + + override def existsData(dynamicEntityName: String): Boolean = { + DynamicData.findAll(By(DynamicData.DynamicEntityName, dynamicEntityName), MaxRows(1)) + .nonEmpty + } +} + +class DynamicData extends DynamicDataT with LongKeyedMapper[DynamicData] with IdPK { + + override def getSingleton = DynamicData + + object DynamicDataId extends MappedUUID(this) + object DynamicEntityName extends MappedString(this, 255) + + object DataJson extends MappedText(this) + + override def dynamicDataId: Option[String] = Option(DynamicDataId.get) + override def dynamicEntityName: String = DynamicEntityName.get + override def dataJson: String = DataJson.get +} + +object DynamicData extends DynamicData with LongKeyedMetaMapper[DynamicData] { + override def dbIndexes = UniqueIndex(DynamicDataId) :: super.dbIndexes +} + diff --git a/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala index 2223463a9..c48a0e09c 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/DynamicEntityTest.scala @@ -202,7 +202,7 @@ class DynamicEntityTest extends V400ServerSetup { // update a not exists DynamicEntity val request404 = (v4_0_0_Request / "management" / "dynamic_entities" / "not-exists-id" ).PUT <@(user1) val response404 = makePutRequest(request404, compactRender(updateRequest)) - Then("We should get a 400") + Then("We should get a 404") response404.code should equal(404) response404.body.extract[ErrorMessage].message should startWith (DynamicEntityNotFoundByDynamicEntityId) } diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/enums/Enumerations.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/enums/Enumerations.scala index ca821c77d..b9c2d461c 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/enums/Enumerations.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/enums/Enumerations.scala @@ -74,4 +74,5 @@ object DynamicEntityOperation extends OBPEnumeration[DynamicEntityOperation] { object CREATE extends Value object UPDATE extends Value object DELETE extends Value + object IS_EXISTS_DATA extends Value } \ No newline at end of file